From 026191e8cd5ef5e05308f1717d472f5a5d3c653f Mon Sep 17 00:00:00 2001 From: ncomerci Date: Wed, 12 Apr 2023 17:21:12 -0300 Subject: [PATCH 01/23] started strategy --- src/strategies/index.ts | 4 +- src/strategies/multidelegation/examples.json | 116 ++++++++++ src/strategies/multidelegation/index.ts | 74 +++++++ src/strategies/multidelegation/utils.ts | 11 + test/multidelegation.test.ts | 216 +++++++++++++++++++ 5 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 src/strategies/multidelegation/examples.json create mode 100644 src/strategies/multidelegation/index.ts create mode 100644 src/strategies/multidelegation/utils.ts create mode 100644 test/multidelegation.test.ts diff --git a/src/strategies/index.ts b/src/strategies/index.ts index 8942c8f8f..f127d4bc9 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -434,6 +434,7 @@ import * as izumiVeiZi from './izumi-veizi'; import * as lqtyProxyStakers from './lqty-proxy-stakers'; import * as echelonWalletPrimeAndCachedKeyGated from './echelon-wallet-prime-and-cached-key-gated'; import * as rdntCapitalVoting from './rdnt-capital-voting'; +import * as multidelegation from './multidelegation'; const strategies = { 'izumi-veizi': izumiVeiZi, @@ -874,7 +875,8 @@ const strategies = { 'lqty-proxy-stakers': lqtyProxyStakers, 'echelon-wallet-prime-and-cached-key-gated': echelonWalletPrimeAndCachedKeyGated, - 'rdnt-capital-voting': rdntCapitalVoting + 'rdnt-capital-voting': rdntCapitalVoting, + multidelegation }; Object.keys(strategies).forEach(function (strategyName) { diff --git a/src/strategies/multidelegation/examples.json b/src/strategies/multidelegation/examples.json new file mode 100644 index 000000000..27d463b79 --- /dev/null +++ b/src/strategies/multidelegation/examples.json @@ -0,0 +1,116 @@ +[ + { + "name": "Example query", + "strategy": { + "name": "multidelegation", + "params": { + "symbol": "VP (delegated)", + "strategies": [ + { + "name": "erc20-balance-of", + "params": { + "symbol": "WMANA", + "address": "0xfd09cf7cfffa9932e33668311c4777cb9db3c9be", + "decimals": 18 + } + }, + { + "name": "erc721-with-multiplier", + "params": { + "symbol": "LAND", + "address": "0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d", + "multiplier": 2000 + } + }, + { + "name": "decentraland-estate-size", + "params": { + "symbol": "ESTATE", + "address": "0x959e104e1a4db6317fa58f8295f586e1a978c297", + "multiplier": 2000 + } + }, + { + "name": "multichain", + "params": { + "name": "multichain", + "graphs": { + "137": "https://api.thegraph.com/subgraphs/name/decentraland/blocks-matic-mainnet" + }, + "symbol": "MANA", + "strategies": [ + { + "name": "erc20-balance-of", + "params": { + "address": "0x0f5d2fb29fb7d3cfee444a200298f468908cc942", + "decimals": 18 + }, + "network": "1" + }, + { + "name": "erc20-balance-of", + "params": { + "address": "0xA1c57f48F0Deb89f569dFbE6E2B7f46D33606fD4", + "decimals": 18 + }, + "network": "137" + } + ] + } + }, + { + "name": "erc721-with-multiplier", + "params": { + "symbol": "NAMES", + "address": "0x2a187453064356c898cae034eaed119e1663acb8", + "multiplier": 100 + } + }, + { + "name": "decentraland-wearable-rarity", + "params": { + "symbol": "WEARABLE", + "collections": [ + "0x32b7495895264ac9d0b12d32afd435453458b1c6", + "0xd35147be6401dcb20811f2104c33de8e97ed6818", + "0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd", + "0xc1f4b0eea2bd6690930e6c66efd3e197d620b9c2", + "0xf64dc33a192e056bb5f0e5049356a0498b502d50", + "0xc3af02c0fd486c8e9da5788b915d6fff3f049866" + ], + "multipliers": { + "epic": 10, + "rare": 5, + "mythic": 1000, + "uncommon": 1, + "legendary": 100 + } + } + }, + { + "name": "decentraland-rental-lessors", + "params": { + "symbol": "RENTAL", + "addresses": { + "land": "0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d", + "estate": "0x959e104e1a4db6317fa58f8295f586e1a978c297" + }, + "subgraphs": { + "rentals": "https://api.thegraph.com/subgraphs/name/decentraland/rentals-ethereum-mainnet", + "marketplace": "https://api.thegraph.com/subgraphs/name/decentraland/marketplace" + }, + "multipliers": { "land": 2000, "estateSize": 2000 } + } + } + ] + } + }, + "network": "1", + "addresses": [ + "0x3c13f2B56AF614aC6381265EcB3B619bA26CC641", + "0x048fee7c3279a24af0790b6b002ded42be021d2b", + "0x139a9032a46c3afe3456eb5f0a35183b5f189cae" + ], + "snapshot": 15705816 + } +] \ No newline at end of file diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts new file mode 100644 index 000000000..a34946f93 --- /dev/null +++ b/src/strategies/multidelegation/index.ts @@ -0,0 +1,74 @@ +import { getScoresDirect } from '../../utils'; +import { strategy as legacyDelegationStrategy } from '../delegation'; +import { getMultiDelegations } from './utils'; + +export const author = 'dcl-DAO'; +export const version = '0.1.0'; +export const dependOnOtherAddress = true; + +export async function strategy( + space, + network, + provider, + addresses, + options, + snapshot +) { + // Retro compatibility with the legacy delegation strategy + const legacyDelegationsPromise = legacyDelegationStrategy( + space, + network, + provider, + addresses, + options, + snapshot + ); + + const delegationSpace = options.delegationSpace || space; + const multiDelegationsPromise = getMultiDelegations( + delegationSpace, + network, + addresses, + snapshot + ); + + const [legacyDelegations, multiDelegations] = await Promise.all([ + legacyDelegationsPromise, + multiDelegationsPromise + ]); + + const isLegacyDelegationEmpty = Object.keys(legacyDelegations).length === 0; + const isMultiDelegationEmpty = Object.keys(multiDelegations).length === 0; + + if (isLegacyDelegationEmpty && isMultiDelegationEmpty) return {}; + + if (isMultiDelegationEmpty) return legacyDelegations; + + // TODO: check if getScoresDirect can be called with multiDelegations + const scores = ( + await getScoresDirect( + space, + options.strategies, + network, + provider, + Object.values(multiDelegations).reduce((a: string[], b: string[]) => + a.concat(b) + ), + snapshot + ) + ).filter((score) => Object.keys(score).length !== 0); + + return Object.fromEntries( + addresses.map((address) => { + const addressScore = + legacyDelegations[address] || + (multiDelegations[address] + ? multiDelegations[address].reduce( + (a, b) => a + scores.reduce((x, y) => (y[b] ? x + y[b] : x), 0), + 0 + ) + : 0); + return [address, addressScore]; + }) + ); +} diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts new file mode 100644 index 000000000..81f45f27a --- /dev/null +++ b/src/strategies/multidelegation/utils.ts @@ -0,0 +1,11 @@ +export async function getMultiDelegations( + space, + network, + addresses, + snapshot +): Promise<{ + [k: string]: any; +}> { + // TODO: implement this function + return {}; +} diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts new file mode 100644 index 000000000..2fdcb06e3 --- /dev/null +++ b/test/multidelegation.test.ts @@ -0,0 +1,216 @@ +import { strategy } from '../src/strategies/multidelegation/index'; +import * as legacyDelegationStrategy from '../src/strategies/delegation/index'; +import * as multidelegationUtils from '../src/strategies/multidelegation/utils'; +import * as utils from '../src/utils'; + +const SPACE = 'space1'; +const NETWORK = '1'; +const PROVIDER = 'provider1'; +const ADDRESSES = ['address1', 'address2']; +const OPTIONS = { + strategies: [ + { + name: 'erc20-balance-of', + params: { + symbol: 'WMANA', + address: '0xfd09cf7cfffa9932e33668311c4777cb9db3c9be', + decimals: 18 + } + } + ] +}; +const SNAPSHOT = 'snapshot1'; + +function mockLegacyDelegation(result: { [k: string]: any }) { + return jest + .spyOn(legacyDelegationStrategy, 'strategy') + .mockResolvedValue(result); +} + +function mockGetMultiDelegations(result: { [k: string]: any }) { + return jest + .spyOn(multidelegationUtils, 'getMultiDelegations') + .mockResolvedValue(result); +} + +function mockGetScoresDirect(result: Record[]) { + return jest.spyOn(utils, 'getScoresDirect').mockResolvedValue(result); +} + +// Test case 1: Test for empty legacyDelegations and multiDelegations +test('Test for empty legacyDelegations and multiDelegations', async () => { + // Mock the legacyDelegationStrategy and getMultiDelegations functions + const legacyDelegation = mockLegacyDelegation({}); + const getMultiDelegations = mockGetMultiDelegations({}); + + // Call the strategy function + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); + + // Expectations + expect(legacyDelegation).toHaveBeenCalledWith( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); + expect(getMultiDelegations).toHaveBeenCalledWith( + SPACE, + NETWORK, + ADDRESSES, + SNAPSHOT + ); + expect(result).toEqual({}); +}); + +// Test case 2: Test for non-empty legacyDelegations and empty multiDelegations +test('Test for non-empty legacyDelegations and empty multiDelegations', async () => { + // Mock the legacyDelegationStrategy and getMultiDelegations functions + const legacyDelegationStrategy = mockLegacyDelegation({ + address1: 100, + address2: 200 + }); + const getMultiDelegations = mockGetMultiDelegations({}); + + // Call the strategy function + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); + + // Expectations + expect(legacyDelegationStrategy).toHaveBeenCalledWith( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); + expect(getMultiDelegations).toHaveBeenCalledWith( + SPACE, + NETWORK, + ADDRESSES, + SNAPSHOT + ); + expect(result).toEqual({ address1: 100, address2: 200 }); +}); + +// Test case 3: Test for empty legacyDelegations and non-empty multiDelegations +test('Test for empty legacyDelegations and non-empty multiDelegations', async () => { + // Mock the legacyDelegationStrategy and getMultiDelegations functions + const legacyDelegationStrategy = mockLegacyDelegation({}); + const getMultiDelegations = mockGetMultiDelegations({ + address1: ['delegate1'], + address2: ['delegate2'] + }); + + // Mock the getScoresDirect function + const getScoresDirect = mockGetScoresDirect([ + { delegate1: 50 }, + { delegate2: 100 } + ]); + + // Call the strategy function + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); + + // Expectations + expect(legacyDelegationStrategy).toHaveBeenCalledWith( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); + expect(getMultiDelegations).toHaveBeenCalledWith( + SPACE, + NETWORK, + ADDRESSES, + SNAPSHOT + ); + expect(getScoresDirect).toHaveBeenCalledWith( + SPACE, + OPTIONS.strategies, + NETWORK, + PROVIDER, + ['delegate1', 'delegate2'], + SNAPSHOT + ); + expect(result).toMatchObject({ address1: 50, address2: 100 }); +}); + +// Test case 4: Test for non-empty legacyDelegations and non-empty multiDelegations +test('Test for non-empty legacyDelegations and non-empty multiDelegations', async () => { + // Mock the legacyDelegationStrategy and getMultiDelegations functions + const legacyDelegationStrategy = mockLegacyDelegation({ + address1: 100 + }); + const getMultiDelegations = mockGetMultiDelegations({ + address1: ['delegate1'], + address2: ['delegate2'] + }); + + // Mock the getScoresDirect function + const getScoresDirect = mockGetScoresDirect([ + { delegate1: 50 }, + { delegate2: 100 } + ]); + + // Call the strategy function + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); + + // Expectations + expect(legacyDelegationStrategy).toHaveBeenCalledWith( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); + expect(getMultiDelegations).toHaveBeenCalledWith( + SPACE, + NETWORK, + ADDRESSES, + SNAPSHOT + ); + expect(getScoresDirect).toHaveBeenCalledWith( + SPACE, + OPTIONS.strategies, + NETWORK, + PROVIDER, + ['delegate1', 'delegate2'], + SNAPSHOT + ); + expect(result).toEqual({ + address1: 100, + address2: 100 + }); +}); From 5e2e9101990d8ef2e7117d44db6505422815bae6 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Wed, 12 Apr 2023 19:01:01 -0300 Subject: [PATCH 02/23] testing --- src/strategies/multidelegation/utils.ts | 77 +++++++++++++++++++++++-- src/testScript.ts | 12 ++++ 2 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 src/testScript.ts diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index 81f45f27a..825cc5e55 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -1,11 +1,76 @@ +import { getAddress } from '@ethersproject/address'; +import { subgraphRequest } from '../../utils'; + +export async function getDelegatesBySpace( + network: string, + space: string, + snapshot = 'latest' +) { + const spaceIn = ['', space]; + if (space.includes('.eth')) spaceIn.push(space.replace('.eth', '')); + + const PAGE_SIZE = 1000; + let result: { delegator: string; space: string; delegate: string }[] = []; + let page = 0; + const params: any = { + delegations: { + __args: { + where: { + space_in: spaceIn + }, + first: PAGE_SIZE, + skip: 0 + }, + delegator: true, + space: true, + delegate: true + } + }; + if (snapshot !== 'latest') { + params.delegations.__args.block = { number: snapshot }; + } + + while (true) { + params.delegations.__args.skip = page * PAGE_SIZE; + + const pageResult = await subgraphRequest( + 'https://api.thegraph.com/subgraphs/name/andyesp/multi-delegation', + params + ); + const pageDelegations = pageResult.delegations || []; + result = result.concat(pageDelegations); + page++; + if (pageDelegations.length < PAGE_SIZE) break; + } + + return result; +} + export async function getMultiDelegations( - space, - network, - addresses, - snapshot + space: string, + network: string, + addresses: string[], + snapshot?: string ): Promise<{ [k: string]: any; }> { - // TODO: implement this function - return {}; + const delegatesBySpace = await getDelegatesBySpace(network, space, snapshot); + + const delegationsReverse: Record> = {}; + delegatesBySpace.forEach( + (delegation) => + (delegationsReverse[delegation.delegator] = new Set([ + ...(delegationsReverse[delegation.delegator] || []), + delegation.delegate + ])) + ); + + return Object.fromEntries( + addresses.map((address) => [ + address.toLowerCase(), + Object.entries(delegationsReverse) + .filter(([, delegates]) => delegates.has(address.toLowerCase())) + .map(([delegator]) => getAddress(delegator)) + ]) + ); } diff --git a/src/testScript.ts b/src/testScript.ts new file mode 100644 index 000000000..63f8cea1b --- /dev/null +++ b/src/testScript.ts @@ -0,0 +1,12 @@ +import { getMultiDelegations } from './strategies/multidelegation/utils'; + +async function fn() { + console.log( + await getMultiDelegations('1emu.eth', '1', [ + '0x549a9021661a85b6bc51c07b3a451135848D0048', + '0xbf363aedd082ddd8db2d6457609b03f9ee74A2f1' + ]) + ); +} + +fn(); From b59574b986b6f1162c7d227c44dfa8897a969248 Mon Sep 17 00:00:00 2001 From: lemu Date: Mon, 22 May 2023 16:35:00 -0300 Subject: [PATCH 03/23] chore: multidelegation wip --- src/strategies/multidelegation/index.ts | 90 ++++++++++-------- src/strategies/multidelegation/utils.ts | 4 +- test/multidelegation.test.ts | 121 ++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 39 deletions(-) diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index a34946f93..b8752d93f 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -1,11 +1,27 @@ -import { getScoresDirect } from '../../utils'; -import { strategy as legacyDelegationStrategy } from '../delegation'; +// @ts-nocheck import { getMultiDelegations } from './utils'; +import { getDelegations } from '../../utils/delegation'; +import fetch from 'cross-fetch'; export const author = 'dcl-DAO'; export const version = '0.1.0'; export const dependOnOtherAddress = true; +async function getPolygonBlockNumber(snapshot) { + const result = await fetch( + `https://api.etherscan.io/api?module=block&action=getblockreward&blockno=${snapshot}` + ); + + const fullResult = await result.json(); + const timestamp = fullResult.result.timeStamp; + const anotherResult = await fetch( + `https://api-testnet.polygonscan.com/api?module=block&action=getblocknobytime×tamp=${timestamp}&closest=before` + ); + + const anotherFullResult = await anotherResult.json(); + return anotherFullResult.result; +} + export async function strategy( space, network, @@ -14,22 +30,22 @@ export async function strategy( options, snapshot ) { + const delegationSpace = options.delegationSpace || space; + // Retro compatibility with the legacy delegation strategy - const legacyDelegationsPromise = legacyDelegationStrategy( - space, - network, - provider, + const legacyDelegationsPromise = getDelegations( + 'lemu.dcl.eth', + 1, addresses, - options, snapshot ); - const delegationSpace = options.delegationSpace || space; + const polygonBlockNumber = await getPolygonBlockNumber(snapshot); const multiDelegationsPromise = getMultiDelegations( delegationSpace, network, addresses, - snapshot + polygonBlockNumber ); const [legacyDelegations, multiDelegations] = await Promise.all([ @@ -42,33 +58,33 @@ export async function strategy( if (isLegacyDelegationEmpty && isMultiDelegationEmpty) return {}; - if (isMultiDelegationEmpty) return legacyDelegations; - - // TODO: check if getScoresDirect can be called with multiDelegations - const scores = ( - await getScoresDirect( - space, - options.strategies, - network, - provider, - Object.values(multiDelegations).reduce((a: string[], b: string[]) => - a.concat(b) - ), - snapshot - ) - ).filter((score) => Object.keys(score).length !== 0); + return [legacyDelegations, multiDelegations]; - return Object.fromEntries( - addresses.map((address) => { - const addressScore = - legacyDelegations[address] || - (multiDelegations[address] - ? multiDelegations[address].reduce( - (a, b) => a + scores.reduce((x, y) => (y[b] ? x + y[b] : x), 0), - 0 - ) - : 0); - return [address, addressScore]; - }) - ); + // // TODO: check if getScoresDirect can be called with multiDelegations + // const scores = ( + // await getScoresDirect( + // space, + // options.strategies, + // network, + // provider, + // Object.values(multiDelegations).reduce((a: string[], b: string[]) => + // a.concat(b) + // ), + // snapshot + // ) + // ).filter((score) => Object.keys(score).length !== 0); + // + // return Object.fromEntries( + // addresses.map((address) => { + // const addressScore = + // legacyDelegations[address] || + // (multiDelegations[address] + // ? multiDelegations[address].reduce( + // (a, b) => a + scores.reduce((x, y) => (y[b] ? x + y[b] : x), 0), + // 0 + // ) + // : 0); + // return [address, addressScore]; + // }) + // ); } diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index 825cc5e55..6efa00ccc 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -27,14 +27,14 @@ export async function getDelegatesBySpace( } }; if (snapshot !== 'latest') { - params.delegations.__args.block = { number: snapshot }; + params.delegations.__args.block = { number: Number(snapshot) }; } while (true) { params.delegations.__args.skip = page * PAGE_SIZE; const pageResult = await subgraphRequest( - 'https://api.thegraph.com/subgraphs/name/andyesp/multi-delegation', + 'https://api.thegraph.com/subgraphs/name/1emu/multi-delegation-polygon', params ); const pageDelegations = pageResult.delegations || []; diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index 2fdcb06e3..48fb4750c 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -2,6 +2,8 @@ import { strategy } from '../src/strategies/multidelegation/index'; import * as legacyDelegationStrategy from '../src/strategies/delegation/index'; import * as multidelegationUtils from '../src/strategies/multidelegation/utils'; import * as utils from '../src/utils'; +// import { getScoresDirect } from '../src/utils'; +import snapshot from '../src'; const SPACE = 'space1'; const NETWORK = '1'; @@ -16,6 +18,100 @@ const OPTIONS = { address: '0xfd09cf7cfffa9932e33668311c4777cb9db3c9be', decimals: 18 } + }, + { + name: 'erc721-with-multiplier', + params: { + symbol: 'LAND', + address: '0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d', + multiplier: 2000 + } + }, + { + name: 'decentraland-estate-size', + params: { + symbol: 'ESTATE', + address: '0x959e104e1a4db6317fa58f8295f586e1a978c297', + multiplier: 2000 + } + }, + { + name: 'multichain', + params: { + name: 'multichain', + graphs: { + '137': + 'https://api.thegraph.com/subgraphs/name/decentraland/blocks-matic-mainnet' + }, + symbol: 'MANA', + strategies: [ + { + name: 'erc20-balance-of', + params: { + address: '0x0f5d2fb29fb7d3cfee444a200298f468908cc942', + decimals: 18 + }, + network: '1' + }, + { + name: 'erc20-balance-of', + params: { + address: '0xa1c57f48f0deb89f569dfbe6e2b7f46d33606fd4', + decimals: 18 + }, + network: '137' + } + ] + } + }, + { + name: 'erc721-with-multiplier', + params: { + symbol: 'NAMES', + address: '0x2a187453064356c898cae034eaed119e1663acb8', + multiplier: 100 + } + }, + { + name: 'decentraland-wearable-rarity', + params: { + symbol: 'WEARABLE', + collections: [ + '0x32b7495895264ac9d0b12d32afd435453458b1c6', + '0xd35147be6401dcb20811f2104c33de8e97ed6818', + '0xc04528c14c8ffd84c7c1fb6719b4a89853035cdd', + '0xc1f4b0eea2bd6690930e6c66efd3e197d620b9c2', + '0xf64dc33a192e056bb5f0e5049356a0498b502d50', + '0xc3af02c0fd486c8e9da5788b915d6fff3f049866' + ], + multipliers: { + epic: 10, + rare: 5, + mythic: 1000, + uncommon: 1, + legendary: 100 + } + } + }, + { + name: 'decentraland-rental-lessors', + params: { + symbol: 'RENTAL', + addresses: { + land: '0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d', + estate: '0x959e104e1a4db6317fa58f8295f586e1a978c297' + }, + subgraphs: { + rentals: + 'https://api.thegraph.com/subgraphs/name/decentraland/rentals-ethereum-mainnet', + marketplace: + 'https://api.thegraph.com/subgraphs/name/decentraland/marketplace' + }, + multipliers: { + land: 2000, + estateSize: 2000 + } + } } ] }; @@ -214,3 +310,28 @@ test('Test for non-empty legacyDelegations and non-empty multiDelegations', asyn address2: 100 }); }); + +test('pruebita', async () => { + const SNAPSHOT = 17316799; + const NETWORK = '1'; + const PROVIDER = snapshot.utils.getProvider(NETWORK); + const SPACE = '1emu.eth'; + const ADDRESSES = [ + '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F', + '0x549A9021661a85B6BC51c07B3A451135848d0048', + '0x30b1f4bd5476906f38385b891f2c09973196b742' + ]; + + // [{address1: vp, address2: score}, {address1: 0}] + + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); + + console.log('result', result); +}); From de6af1a74f57383897cb06b3784c2e98d9e0c6ef Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 23 May 2023 15:11:07 -0300 Subject: [PATCH 04/23] chore: changed forEach for reduce --- src/strategies/multidelegation/utils.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index 6efa00ccc..de37b0c2f 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -56,14 +56,13 @@ export async function getMultiDelegations( }> { const delegatesBySpace = await getDelegatesBySpace(network, space, snapshot); - const delegationsReverse: Record> = {}; - delegatesBySpace.forEach( - (delegation) => - (delegationsReverse[delegation.delegator] = new Set([ - ...(delegationsReverse[delegation.delegator] || []), - delegation.delegate - ])) - ); + const delegationsReverse = delegatesBySpace.reduce((accum, delegation) => { + accum[delegation.delegator] = new Set([ + ...(accum[delegation.delegator] || []), + delegation.delegate + ]); + return accum; + }, {} as Record>); return Object.fromEntries( addresses.map((address) => [ From f2a07b893198f88b9c8d8d281253ecc20c62c61b Mon Sep 17 00:00:00 2001 From: lemu Date: Wed, 24 May 2023 11:40:09 -0300 Subject: [PATCH 05/23] chore: multidelegation wip --- src/strategies/multidelegation/index.ts | 62 ++++++++++++++++--------- src/strategies/multidelegation/utils.ts | 58 +++++++++++++++++------ test/multidelegation.test.ts | 6 ++- 3 files changed, 87 insertions(+), 39 deletions(-) diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index b8752d93f..5b63f7888 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -1,25 +1,37 @@ // @ts-nocheck -import { getMultiDelegations } from './utils'; -import { getDelegations } from '../../utils/delegation'; -import fetch from 'cross-fetch'; +import { getLegacyDelegations, getMultiDelegations } from './utils'; +import { getSnapshots } from '../../utils'; +import { getAddress } from '@ethersproject/address'; export const author = 'dcl-DAO'; export const version = '0.1.0'; export const dependOnOtherAddress = true; -async function getPolygonBlockNumber(snapshot) { - const result = await fetch( - `https://api.etherscan.io/api?module=block&action=getblockreward&blockno=${snapshot}` - ); +function mergeDelegations( + legacyDelegations: Record, + multiDelegations: Record +) { + const mergedDelegations: Record = {}; - const fullResult = await result.json(); - const timestamp = fullResult.result.timeStamp; - const anotherResult = await fetch( - `https://api-testnet.polygonscan.com/api?module=block&action=getblocknobytime×tamp=${timestamp}&closest=before` - ); + const delegators = new Set([ + ...Object.keys(legacyDelegations), + ...Object.keys(multiDelegations) + ]); + + // Iterate over legacyDelegations + for (const delegator of delegators) { + const legacyDelegate = legacyDelegations[delegator]; + const multiDelegates = multiDelegations[delegator]; + + // Check if multiDelegations has a list for the current address + if (!!multiDelegates && multiDelegates.length > 0) { + mergedDelegations[delegator] = multiDelegates; + } else { + mergedDelegations[delegator] = [legacyDelegate]; + } + } - const anotherFullResult = await anotherResult.json(); - return anotherFullResult.result; + return mergedDelegations; } export async function strategy( @@ -31,20 +43,26 @@ export async function strategy( snapshot ) { const delegationSpace = options.delegationSpace || space; - + const checksummedAddresses = addresses.map(getAddress); // Retro compatibility with the legacy delegation strategy - const legacyDelegationsPromise = getDelegations( - 'lemu.dcl.eth', - 1, - addresses, + const legacyDelegationsPromise = getLegacyDelegations( + 'snapshot.dcl.eth', + network, + checksummedAddresses, snapshot ); - const polygonBlockNumber = await getPolygonBlockNumber(snapshot); + const polygonChainId = '80001'; + const blocks = await getSnapshots(network, snapshot, provider, [ + polygonChainId + ]); + + const polygonBlockNumber = blocks[polygonChainId]; + const multiDelegationsPromise = getMultiDelegations( delegationSpace, network, - addresses, + checksummedAddresses, polygonBlockNumber ); @@ -58,7 +76,7 @@ export async function strategy( if (isLegacyDelegationEmpty && isMultiDelegationEmpty) return {}; - return [legacyDelegations, multiDelegations]; + return mergeDelegations(legacyDelegations, multiDelegations); // // TODO: check if getScoresDirect can be called with multiDelegations // const scores = ( diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index de37b0c2f..9323e61c6 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -1,7 +1,7 @@ import { getAddress } from '@ethersproject/address'; -import { subgraphRequest } from '../../utils'; +import { getDelegatesBySpace, subgraphRequest } from '../../utils'; -export async function getDelegatesBySpace( +export async function getPolygonDelegatesBySpace( network: string, space: string, snapshot = 'latest' @@ -54,22 +54,50 @@ export async function getMultiDelegations( ): Promise<{ [k: string]: any; }> { - const delegatesBySpace = await getDelegatesBySpace(network, space, snapshot); + const delegatesBySpace = await getPolygonDelegatesBySpace( + network, + space, + snapshot + ); const delegationsReverse = delegatesBySpace.reduce((accum, delegation) => { - accum[delegation.delegator] = new Set([ - ...(accum[delegation.delegator] || []), - delegation.delegate - ]); + const delegator = getAddress(delegation.delegator); + accum[delegator] = [ + ...(accum[delegator] || []), + getAddress(delegation.delegate) + ]; return accum; - }, {} as Record>); + }, {} as Record); + + return delegationsReverse; +} - return Object.fromEntries( - addresses.map((address) => [ - address.toLowerCase(), - Object.entries(delegationsReverse) - .filter(([, delegates]) => delegates.has(address.toLowerCase())) - .map(([delegator]) => getAddress(delegator)) - ]) +export async function getLegacyDelegations( + space, + network, + addresses, + snapshot +) { + const delegatesBySpace = await getDelegatesBySpace(network, space, snapshot); + const delegationsReverse = {}; + delegatesBySpace.forEach( + (delegation: any) => + (delegationsReverse[delegation.delegator] = delegation.delegate) ); + delegatesBySpace + .filter((delegation: any) => delegation.space !== '') + .forEach( + (delegation: any) => + (delegationsReverse[delegation.delegator] = delegation.delegate) + ); + + const result = {}; + addresses.forEach((address) => { + const addressLc = address.toLowerCase(); + if (delegationsReverse[addressLc]) { + result[getAddress(addressLc)] = getAddress(delegationsReverse[addressLc]); + } + }); + + return result; } diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index 48fb4750c..8e72e8d1f 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -319,7 +319,9 @@ test('pruebita', async () => { const ADDRESSES = [ '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F', '0x549A9021661a85B6BC51c07B3A451135848d0048', - '0x30b1f4bd5476906f38385b891f2c09973196b742' + '0x30b1f4bd5476906f38385b891f2c09973196b742', + '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d', + '0xb0F847e61C502Fb82D758C515b3F914de42831D5' ]; // [{address1: vp, address2: score}, {address1: 0}] @@ -333,5 +335,5 @@ test('pruebita', async () => { SNAPSHOT ); - console.log('result', result); + console.log('result', JSON.stringify(result)); }); From 8bdfee0100cd46407db23787592c50533d0fcb26 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Wed, 24 May 2023 12:44:02 -0300 Subject: [PATCH 06/23] merge delegations tests --- src/strategies/multidelegation/index.ts | 34 +----- src/strategies/multidelegation/utils.ts | 27 +++++ test/mergeDelegations.test.ts | 150 ++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 29 deletions(-) create mode 100644 test/mergeDelegations.test.ts diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index 5b63f7888..7fdb4791b 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -1,5 +1,8 @@ -// @ts-nocheck -import { getLegacyDelegations, getMultiDelegations } from './utils'; +import { + getLegacyDelegations, + getMultiDelegations, + mergeDelegations +} from './utils'; import { getSnapshots } from '../../utils'; import { getAddress } from '@ethersproject/address'; @@ -7,33 +10,6 @@ export const author = 'dcl-DAO'; export const version = '0.1.0'; export const dependOnOtherAddress = true; -function mergeDelegations( - legacyDelegations: Record, - multiDelegations: Record -) { - const mergedDelegations: Record = {}; - - const delegators = new Set([ - ...Object.keys(legacyDelegations), - ...Object.keys(multiDelegations) - ]); - - // Iterate over legacyDelegations - for (const delegator of delegators) { - const legacyDelegate = legacyDelegations[delegator]; - const multiDelegates = multiDelegations[delegator]; - - // Check if multiDelegations has a list for the current address - if (!!multiDelegates && multiDelegates.length > 0) { - mergedDelegations[delegator] = multiDelegates; - } else { - mergedDelegations[delegator] = [legacyDelegate]; - } - } - - return mergedDelegations; -} - export async function strategy( space, network, diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index 9323e61c6..4dcc176bf 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -101,3 +101,30 @@ export async function getLegacyDelegations( return result; } + +// legacy and multi delegations are both objects with delegator as key and delegate(s) as value +export function mergeDelegations( + legacyDelegations: Record, + multiDelegations: Record +) { + const mergedDelegations: Record = {}; + + const delegators = new Set([ + ...Object.keys(legacyDelegations || {}), + ...Object.keys(multiDelegations || {}) + ]); + + for (const delegator of delegators) { + const legacyDelegate = legacyDelegations[delegator]; + const multiDelegates = multiDelegations[delegator]; + + // Check if multiDelegations has a list for the current address + if (!!multiDelegates && multiDelegates.length > 0) { + mergedDelegations[delegator] = multiDelegates; + } else if (!!legacyDelegate) { + mergedDelegations[delegator] = [legacyDelegate]; + } + } + + return mergedDelegations; +} diff --git a/test/mergeDelegations.test.ts b/test/mergeDelegations.test.ts new file mode 100644 index 000000000..403ac3cad --- /dev/null +++ b/test/mergeDelegations.test.ts @@ -0,0 +1,150 @@ +import * as multidelegationUtils from '../src/strategies/multidelegation/utils'; + +const { mergeDelegations } = multidelegationUtils; + +describe('when both legacyDelegations and multiDelegations are empty objects', () => { + it('returns an empty object', () => { + const legacyDelegations: Record = {}; + const multiDelegations: Record = {}; + const mergedDelegations = mergeDelegations( + legacyDelegations, + multiDelegations + ); + expect(mergedDelegations).toEqual({}); + }); +}); + +describe('when legacyDelegations is not an empty object and multiDelegations is an empty object', () => { + it('returns legacy delegations', () => { + const legacyDelegations: Record = { + '0x123': '0x456', + '0x789': '0xabc' + }; + const multiDelegations: Record = {}; + const mergedDelegations = mergeDelegations( + legacyDelegations, + multiDelegations + ); + expect(mergedDelegations).toEqual({ + '0x123': ['0x456'], + '0x789': ['0xabc'] + }); + }); +}); + +describe('when legacyDelegations is an empty object and multiDelegations is not an empty object', () => { + it('returns multi delegations', () => { + const legacyDelegations: Record = {}; + const multiDelegations: Record = { + '0x123': ['0x456', '0x789'], + '0xxyz': ['0xabc'] + }; + const mergedDelegations = mergeDelegations( + legacyDelegations, + multiDelegations + ); + expect(mergedDelegations).toEqual({ + '0x123': ['0x456', '0x789'], + '0xxyz': ['0xabc'] + }); + }); +}); + +describe('when legacyDelegations and multiDelegations have no common keys', () => { + it('returns merged delegations', () => { + const legacyDelegations: Record = { + '0x123': '0x456', + '0x789': '0xabc' + }; + const multiDelegations: Record = { + '0xxyz': ['0xdef'] + }; + const mergedDelegations = mergeDelegations( + legacyDelegations, + multiDelegations + ); + expect(mergedDelegations).toEqual({ + '0x123': ['0x456'], + '0x789': ['0xabc'], + '0xxyz': ['0xdef'] + }); + }); +}); + +describe('when legacyDelegations and multiDelegations have some common keys', () => { + it('returns merged delegations', () => { + const legacyDelegations: Record = { + '0x123': '0x456', + '0x789': '0xabc' + }; + const multiDelegations: Record = { + '0x123': ['0x789', '0xdef'], + '0xxyz': ['0x123'] + }; + const mergedDelegations = mergeDelegations( + legacyDelegations, + multiDelegations + ); + expect(mergedDelegations).toEqual({ + '0x123': ['0x789', '0xdef'], + '0x789': ['0xabc'], + '0xxyz': ['0x123'] + }); + }); +}); + +describe('when legacyDelegations or multiDelegations is null or undefined', () => { + it('returns an empty object', () => { + const legacyDelegations = null; + const multiDelegations = undefined; + const mergedDelegations = mergeDelegations( + legacyDelegations as any, + multiDelegations as any + ); + expect(mergedDelegations).toEqual({}); + }); +}); + +// Test when a multiDelegation has an empty array and has a common key with legacyDelegations +describe('when a multiDelegation has an empty array and has a common key with legacyDelegations', () => { + it('returns merged delegations', () => { + const legacyDelegations: Record = { + '0x123': '0x456', + '0x789': '0xabc' + }; + const multiDelegations: Record = { + '0x123': [], + '0xxyz': ['0x123'] + }; + const mergedDelegations = mergeDelegations( + legacyDelegations, + multiDelegations + ); + expect(mergedDelegations).toEqual({ + '0x123': ['0x456'], + '0x789': ['0xabc'], + '0xxyz': ['0x123'] + }); + }); +}); + +// Test when a multiDelegation has an empty array and has no common key with legacyDelegations +describe('when a multiDelegation has an empty array and has no common key with legacyDelegations', () => { + it('returns merged delegations', () => { + const legacyDelegations: Record = { + '0x123': '0x456', + '0x789': '0xabc' + }; + const multiDelegations: Record = { + '0xxyz': [] + }; + const mergedDelegations = mergeDelegations( + legacyDelegations, + multiDelegations + ); + expect(mergedDelegations).toEqual({ + '0x123': ['0x456'], + '0x789': ['0xabc'] + }); + }); +}); From a8796f0f4c917e47d55bfda4b46c04848a889c2a Mon Sep 17 00:00:00 2001 From: lemu Date: Wed, 24 May 2023 13:24:25 -0300 Subject: [PATCH 07/23] chore: improve performance by using map instead of record --- src/strategies/multidelegation/utils.ts | 81 +++++++------ test/mergeDelegations.test.ts | 154 ++++++++++++------------ 2 files changed, 121 insertions(+), 114 deletions(-) diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index 4dcc176bf..bf37bb747 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -51,51 +51,52 @@ export async function getMultiDelegations( network: string, addresses: string[], snapshot?: string -): Promise<{ - [k: string]: any; -}> { +): Promise> { const delegatesBySpace = await getPolygonDelegatesBySpace( network, space, snapshot ); - const delegationsReverse = delegatesBySpace.reduce((accum, delegation) => { + return delegatesBySpace.reduce((accum, delegation) => { const delegator = getAddress(delegation.delegator); - accum[delegator] = [ - ...(accum[delegator] || []), - getAddress(delegation.delegate) - ]; + const delegate = getAddress(delegation.delegate); + const existingDelegates = accum.get(delegator) || []; + accum.set(delegator, [...existingDelegates, delegate]); return accum; - }, {} as Record); - - return delegationsReverse; + }, new Map()); } export async function getLegacyDelegations( - space, - network, - addresses, - snapshot -) { + space: string, + network: string, + addresses: string[], + snapshot: string +): Promise> { const delegatesBySpace = await getDelegatesBySpace(network, space, snapshot); - const delegationsReverse = {}; - delegatesBySpace.forEach( - (delegation: any) => - (delegationsReverse[delegation.delegator] = delegation.delegate) - ); + const delegationsReverse = new Map(); + + delegatesBySpace.forEach((delegation: any) => { + const delegator = delegation.delegator.toLowerCase(); + const delegate = delegation.delegate.toLowerCase(); + delegationsReverse.set(delegator, delegate); + }); + delegatesBySpace .filter((delegation: any) => delegation.space !== '') - .forEach( - (delegation: any) => - (delegationsReverse[delegation.delegator] = delegation.delegate) - ); + .forEach((delegation: any) => { + const delegator = delegation.delegator.toLowerCase(); + const delegate = delegation.delegate.toLowerCase(); + delegationsReverse.set(delegator, delegate); + }); - const result = {}; + const result = new Map(); addresses.forEach((address) => { const addressLc = address.toLowerCase(); - if (delegationsReverse[addressLc]) { - result[getAddress(addressLc)] = getAddress(delegationsReverse[addressLc]); + const delegate = delegationsReverse.get(addressLc); + if (!!delegate) { + const delegator = getAddress(addressLc); + result.set(delegator, getAddress(delegate)); } }); @@ -104,25 +105,25 @@ export async function getLegacyDelegations( // legacy and multi delegations are both objects with delegator as key and delegate(s) as value export function mergeDelegations( - legacyDelegations: Record, - multiDelegations: Record -) { - const mergedDelegations: Record = {}; + legacyDelegations: Map, + multiDelegations: Map +): Map { + const mergedDelegations: Map = new Map(); const delegators = new Set([ - ...Object.keys(legacyDelegations || {}), - ...Object.keys(multiDelegations || {}) + ...(legacyDelegations?.keys() || []), + ...(multiDelegations?.keys() || []) ]); for (const delegator of delegators) { - const legacyDelegate = legacyDelegations[delegator]; - const multiDelegates = multiDelegations[delegator]; + const legacyDelegate = legacyDelegations.get(delegator); + const multiDelegates = multiDelegations.get(delegator); // Check if multiDelegations has a list for the current address - if (!!multiDelegates && multiDelegates.length > 0) { - mergedDelegations[delegator] = multiDelegates; - } else if (!!legacyDelegate) { - mergedDelegations[delegator] = [legacyDelegate]; + if (multiDelegates && multiDelegates.length > 0) { + mergedDelegations.set(delegator, multiDelegates); + } else if (legacyDelegate) { + mergedDelegations.set(delegator, [legacyDelegate]); } } diff --git a/test/mergeDelegations.test.ts b/test/mergeDelegations.test.ts index 403ac3cad..7f96b74b8 100644 --- a/test/mergeDelegations.test.ts +++ b/test/mergeDelegations.test.ts @@ -1,95 +1,99 @@ -import * as multidelegationUtils from '../src/strategies/multidelegation/utils'; - -const { mergeDelegations } = multidelegationUtils; +import { mergeDelegations } from '../src/strategies/multidelegation/utils'; describe('when both legacyDelegations and multiDelegations are empty objects', () => { it('returns an empty object', () => { - const legacyDelegations: Record = {}; - const multiDelegations: Record = {}; + const legacyDelegations = new Map(); + const multiDelegations = new Map(); const mergedDelegations = mergeDelegations( legacyDelegations, multiDelegations ); - expect(mergedDelegations).toEqual({}); + expect(mergedDelegations).toEqual(new Map()); }); }); describe('when legacyDelegations is not an empty object and multiDelegations is an empty object', () => { it('returns legacy delegations', () => { - const legacyDelegations: Record = { - '0x123': '0x456', - '0x789': '0xabc' - }; - const multiDelegations: Record = {}; + const legacyDelegations = new Map([ + ['0x123', '0x456'], + ['0x789', '0xabc'] + ]); + const multiDelegations = new Map(); const mergedDelegations = mergeDelegations( legacyDelegations, multiDelegations ); - expect(mergedDelegations).toEqual({ - '0x123': ['0x456'], - '0x789': ['0xabc'] - }); + expect(mergedDelegations).toEqual( + new Map([ + ['0x123', ['0x456']], + ['0x789', ['0xabc']] + ]) + ); }); }); describe('when legacyDelegations is an empty object and multiDelegations is not an empty object', () => { it('returns multi delegations', () => { - const legacyDelegations: Record = {}; - const multiDelegations: Record = { - '0x123': ['0x456', '0x789'], - '0xxyz': ['0xabc'] - }; + const legacyDelegations = new Map(); + const multiDelegations = new Map([ + ['0x123', ['0x456', '0x789']], + ['0xxyz', ['0xabc']] + ]); const mergedDelegations = mergeDelegations( legacyDelegations, multiDelegations ); - expect(mergedDelegations).toEqual({ - '0x123': ['0x456', '0x789'], - '0xxyz': ['0xabc'] - }); + expect(mergedDelegations).toEqual( + new Map([ + ['0x123', ['0x456', '0x789']], + ['0xxyz', ['0xabc']] + ]) + ); }); }); describe('when legacyDelegations and multiDelegations have no common keys', () => { it('returns merged delegations', () => { - const legacyDelegations: Record = { - '0x123': '0x456', - '0x789': '0xabc' - }; - const multiDelegations: Record = { - '0xxyz': ['0xdef'] - }; + const legacyDelegations = new Map([ + ['0x123', '0x456'], + ['0x789', '0xabc'] + ]); + const multiDelegations = new Map([['0xxyz', ['0xdef']]]); const mergedDelegations = mergeDelegations( legacyDelegations, multiDelegations ); - expect(mergedDelegations).toEqual({ - '0x123': ['0x456'], - '0x789': ['0xabc'], - '0xxyz': ['0xdef'] - }); + expect(mergedDelegations).toEqual( + new Map([ + ['0x123', ['0x456']], + ['0x789', ['0xabc']], + ['0xxyz', ['0xdef']] + ]) + ); }); }); describe('when legacyDelegations and multiDelegations have some common keys', () => { it('returns merged delegations', () => { - const legacyDelegations: Record = { - '0x123': '0x456', - '0x789': '0xabc' - }; - const multiDelegations: Record = { - '0x123': ['0x789', '0xdef'], - '0xxyz': ['0x123'] - }; + const legacyDelegations = new Map([ + ['0x123', '0x456'], + ['0x789', '0xabc'] + ]); + const multiDelegations = new Map([ + ['0x123', ['0x789', '0xdef']], + ['0xxyz', ['0x123']] + ]); const mergedDelegations = mergeDelegations( legacyDelegations, multiDelegations ); - expect(mergedDelegations).toEqual({ - '0x123': ['0x789', '0xdef'], - '0x789': ['0xabc'], - '0xxyz': ['0x123'] - }); + expect(mergedDelegations).toEqual( + new Map([ + ['0x123', ['0x789', '0xdef']], + ['0x789', ['0xabc']], + ['0xxyz', ['0x123']] + ]) + ); }); }); @@ -101,50 +105,52 @@ describe('when legacyDelegations or multiDelegations is null or undefined', () = legacyDelegations as any, multiDelegations as any ); - expect(mergedDelegations).toEqual({}); + expect(mergedDelegations).toEqual(new Map()); }); }); // Test when a multiDelegation has an empty array and has a common key with legacyDelegations describe('when a multiDelegation has an empty array and has a common key with legacyDelegations', () => { it('returns merged delegations', () => { - const legacyDelegations: Record = { - '0x123': '0x456', - '0x789': '0xabc' - }; - const multiDelegations: Record = { - '0x123': [], - '0xxyz': ['0x123'] - }; + const legacyDelegations = new Map([ + ['0x123', '0x456'], + ['0x789', '0xabc'] + ]); + const multiDelegations = new Map([ + ['0x123', []], + ['0xxyz', ['0x123']] + ]); const mergedDelegations = mergeDelegations( legacyDelegations, multiDelegations ); - expect(mergedDelegations).toEqual({ - '0x123': ['0x456'], - '0x789': ['0xabc'], - '0xxyz': ['0x123'] - }); + expect(mergedDelegations).toEqual( + new Map([ + ['0x123', ['0x456']], + ['0x789', ['0xabc']], + ['0xxyz', ['0x123']] + ]) + ); }); }); // Test when a multiDelegation has an empty array and has no common key with legacyDelegations describe('when a multiDelegation has an empty array and has no common key with legacyDelegations', () => { it('returns merged delegations', () => { - const legacyDelegations: Record = { - '0x123': '0x456', - '0x789': '0xabc' - }; - const multiDelegations: Record = { - '0xxyz': [] - }; + const legacyDelegations = new Map([ + ['0x123', '0x456'], + ['0x789', '0xabc'] + ]); + const multiDelegations = new Map([['0xxyz', []]]); const mergedDelegations = mergeDelegations( legacyDelegations, multiDelegations ); - expect(mergedDelegations).toEqual({ - '0x123': ['0x456'], - '0x789': ['0xabc'] - }); + expect(mergedDelegations).toEqual( + new Map([ + ['0x123', ['0x456']], + ['0x789', ['0xabc']] + ]) + ); }); }); From e217c5eabbd67fb1f56c6a1b4819db705c16fecf Mon Sep 17 00:00:00 2001 From: lemu Date: Wed, 24 May 2023 13:34:09 -0300 Subject: [PATCH 08/23] chore: reverse delegations WIP --- src/strategies/multidelegation/index.ts | 15 +- test/multidelegation.test.ts | 215 ++---------------------- 2 files changed, 30 insertions(+), 200 deletions(-) diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index 7fdb4791b..a5ddb296e 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -46,13 +46,22 @@ export async function strategy( legacyDelegationsPromise, multiDelegationsPromise ]); + console.log('legacyDelegations', legacyDelegations); + console.log('multiDelegations', multiDelegations); - const isLegacyDelegationEmpty = Object.keys(legacyDelegations).length === 0; - const isMultiDelegationEmpty = Object.keys(multiDelegations).length === 0; + const isLegacyDelegationEmpty = legacyDelegations.size === 0; + const isMultiDelegationEmpty = multiDelegations.size === 0; if (isLegacyDelegationEmpty && isMultiDelegationEmpty) return {}; - return mergeDelegations(legacyDelegations, multiDelegations); + const mergedDelegations = mergeDelegations( + legacyDelegations, + multiDelegations + ); + + console.log('mergedDelegations', mergedDelegations); + + return mergedDelegations; // // TODO: check if getScoresDirect can be called with multiDelegations // const scores = ( diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index 8e72e8d1f..b9e8996bc 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -1,7 +1,6 @@ +// @ts-nocheck + import { strategy } from '../src/strategies/multidelegation/index'; -import * as legacyDelegationStrategy from '../src/strategies/delegation/index'; -import * as multidelegationUtils from '../src/strategies/multidelegation/utils'; -import * as utils from '../src/utils'; // import { getScoresDirect } from '../src/utils'; import snapshot from '../src'; @@ -116,200 +115,22 @@ const OPTIONS = { ] }; const SNAPSHOT = 'snapshot1'; - -function mockLegacyDelegation(result: { [k: string]: any }) { - return jest - .spyOn(legacyDelegationStrategy, 'strategy') - .mockResolvedValue(result); -} - -function mockGetMultiDelegations(result: { [k: string]: any }) { - return jest - .spyOn(multidelegationUtils, 'getMultiDelegations') - .mockResolvedValue(result); -} - -function mockGetScoresDirect(result: Record[]) { - return jest.spyOn(utils, 'getScoresDirect').mockResolvedValue(result); -} - -// Test case 1: Test for empty legacyDelegations and multiDelegations -test('Test for empty legacyDelegations and multiDelegations', async () => { - // Mock the legacyDelegationStrategy and getMultiDelegations functions - const legacyDelegation = mockLegacyDelegation({}); - const getMultiDelegations = mockGetMultiDelegations({}); - - // Call the strategy function - const result = await strategy( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); - - // Expectations - expect(legacyDelegation).toHaveBeenCalledWith( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); - expect(getMultiDelegations).toHaveBeenCalledWith( - SPACE, - NETWORK, - ADDRESSES, - SNAPSHOT - ); - expect(result).toEqual({}); -}); - -// Test case 2: Test for non-empty legacyDelegations and empty multiDelegations -test('Test for non-empty legacyDelegations and empty multiDelegations', async () => { - // Mock the legacyDelegationStrategy and getMultiDelegations functions - const legacyDelegationStrategy = mockLegacyDelegation({ - address1: 100, - address2: 200 - }); - const getMultiDelegations = mockGetMultiDelegations({}); - - // Call the strategy function - const result = await strategy( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); - - // Expectations - expect(legacyDelegationStrategy).toHaveBeenCalledWith( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); - expect(getMultiDelegations).toHaveBeenCalledWith( - SPACE, - NETWORK, - ADDRESSES, - SNAPSHOT - ); - expect(result).toEqual({ address1: 100, address2: 200 }); -}); - -// Test case 3: Test for empty legacyDelegations and non-empty multiDelegations -test('Test for empty legacyDelegations and non-empty multiDelegations', async () => { - // Mock the legacyDelegationStrategy and getMultiDelegations functions - const legacyDelegationStrategy = mockLegacyDelegation({}); - const getMultiDelegations = mockGetMultiDelegations({ - address1: ['delegate1'], - address2: ['delegate2'] - }); - - // Mock the getScoresDirect function - const getScoresDirect = mockGetScoresDirect([ - { delegate1: 50 }, - { delegate2: 100 } - ]); - - // Call the strategy function - const result = await strategy( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); - - // Expectations - expect(legacyDelegationStrategy).toHaveBeenCalledWith( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); - expect(getMultiDelegations).toHaveBeenCalledWith( - SPACE, - NETWORK, - ADDRESSES, - SNAPSHOT - ); - expect(getScoresDirect).toHaveBeenCalledWith( - SPACE, - OPTIONS.strategies, - NETWORK, - PROVIDER, - ['delegate1', 'delegate2'], - SNAPSHOT - ); - expect(result).toMatchObject({ address1: 50, address2: 100 }); -}); - -// Test case 4: Test for non-empty legacyDelegations and non-empty multiDelegations -test('Test for non-empty legacyDelegations and non-empty multiDelegations', async () => { - // Mock the legacyDelegationStrategy and getMultiDelegations functions - const legacyDelegationStrategy = mockLegacyDelegation({ - address1: 100 - }); - const getMultiDelegations = mockGetMultiDelegations({ - address1: ['delegate1'], - address2: ['delegate2'] - }); - - // Mock the getScoresDirect function - const getScoresDirect = mockGetScoresDirect([ - { delegate1: 50 }, - { delegate2: 100 } - ]); - - // Call the strategy function - const result = await strategy( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); - - // Expectations - expect(legacyDelegationStrategy).toHaveBeenCalledWith( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); - expect(getMultiDelegations).toHaveBeenCalledWith( - SPACE, - NETWORK, - ADDRESSES, - SNAPSHOT - ); - expect(getScoresDirect).toHaveBeenCalledWith( - SPACE, - OPTIONS.strategies, - NETWORK, - PROVIDER, - ['delegate1', 'delegate2'], - SNAPSHOT - ); - expect(result).toEqual({ - address1: 100, - address2: 100 - }); -}); +// +// function mockLegacyDelegation(result: { [k: string]: any }) { +// return jest +// .spyOn(legacyDelegationStrategy, 'strategy') +// .mockResolvedValue(result); +// } +// +// function mockGetMultiDelegations(result: { [k: string]: any }) { +// return jest +// .spyOn(multidelegationUtils, 'getMultiDelegations') +// .mockResolvedValue(result); +// } +// +// function mockGetScoresDirect(result: Record[]) { +// return jest.spyOn(utils, 'getScoresDirect').mockResolvedValue(result); +// } test('pruebita', async () => { const SNAPSHOT = 17316799; From ac38451b1d8e5a1f1a167dc03d301d5153895128 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Wed, 24 May 2023 17:02:09 -0300 Subject: [PATCH 09/23] strategy working --- src/strategies/multidelegation/examples.json | 10 ++-- src/strategies/multidelegation/index.ts | 63 ++++++++++---------- src/strategies/multidelegation/utils.ts | 40 ++++++++++++- test/mergeDelegations.test.ts | 2 - test/multidelegation.test.ts | 2 +- 5 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/strategies/multidelegation/examples.json b/src/strategies/multidelegation/examples.json index 27d463b79..1b1e284bf 100644 --- a/src/strategies/multidelegation/examples.json +++ b/src/strategies/multidelegation/examples.json @@ -107,10 +107,12 @@ }, "network": "1", "addresses": [ - "0x3c13f2B56AF614aC6381265EcB3B619bA26CC641", - "0x048fee7c3279a24af0790b6b002ded42be021d2b", - "0x139a9032a46c3afe3456eb5f0a35183b5f189cae" + "0x6cd7694d30c10bdab1e644fc1964043a95ceea5f", + "0x549a9021661a85b6bc51c07b3a451135848d0048", + "0x30b1f4bd5476906f38385b891f2c09973196b742", + "0x511a22cdd2c4ee8357bb02df2578037ffe8a4d8d", + "0xb0f847e61c502fb82d758c515b3f914de42831d5" ], - "snapshot": 15705816 + "snapshot": 17331246 } ] \ No newline at end of file diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index a5ddb296e..8d4dec6a0 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -1,9 +1,11 @@ import { + getDelegateScore, getLegacyDelegations, getMultiDelegations, - mergeDelegations + mergeDelegations, + reverseDelegations } from './utils'; -import { getSnapshots } from '../../utils'; +import { getScoresDirect, getSnapshots } from '../../utils'; import { getAddress } from '@ethersproject/address'; export const author = 'dcl-DAO'; @@ -61,33 +63,34 @@ export async function strategy( console.log('mergedDelegations', mergedDelegations); - return mergedDelegations; + const reversedDelegations = reverseDelegations(mergedDelegations); + console.log('reversedDelegations', reversedDelegations); - // // TODO: check if getScoresDirect can be called with multiDelegations - // const scores = ( - // await getScoresDirect( - // space, - // options.strategies, - // network, - // provider, - // Object.values(multiDelegations).reduce((a: string[], b: string[]) => - // a.concat(b) - // ), - // snapshot - // ) - // ).filter((score) => Object.keys(score).length !== 0); - // - // return Object.fromEntries( - // addresses.map((address) => { - // const addressScore = - // legacyDelegations[address] || - // (multiDelegations[address] - // ? multiDelegations[address].reduce( - // (a, b) => a + scores.reduce((x, y) => (y[b] ? x + y[b] : x), 0), - // 0 - // ) - // : 0); - // return [address, addressScore]; - // }) - // ); + const delegationAddresses = Array.from(reversedDelegations.values()).reduce( + (accumulator, addresses) => accumulator.concat(addresses), + [] + ); + + const scores = ( + await getScoresDirect( + space, + options.strategies, + network, + provider, + delegationAddresses, + snapshot + ) + ).filter((score) => Object.keys(score).length !== 0); + + console.log('scores', scores); + + return Object.fromEntries( + addresses.map((delegate) => { + const delegations = reversedDelegations.get(delegate); + const delegateScore = delegations + ? getDelegateScore(delegations, scores) + : 0; + return [delegate, delegateScore]; + }) + ); } diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index bf37bb747..94967c381 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -119,7 +119,6 @@ export function mergeDelegations( const legacyDelegate = legacyDelegations.get(delegator); const multiDelegates = multiDelegations.get(delegator); - // Check if multiDelegations has a list for the current address if (multiDelegates && multiDelegates.length > 0) { mergedDelegations.set(delegator, multiDelegates); } else if (legacyDelegate) { @@ -129,3 +128,42 @@ export function mergeDelegations( return mergedDelegations; } + +// delegations is an object with delegator as key and delegate(s) as value +export function reverseDelegations(delegations: Map) { + const invertedDelegations = new Map(); + + for (const [delegator, delegates] of delegations) { + for (const delegate of delegates) { + if (invertedDelegations.has(delegate)) { + invertedDelegations.get(delegate)?.push(delegator); + } else { + invertedDelegations.set(delegate, [delegator]); + } + } + } + + return invertedDelegations; +} + +export function getDelegatorScores( + scores: Record[], + delegatorAddress: string +) { + return scores.reduce((strategiesAcumScore, strategyScores) => { + return strategyScores[delegatorAddress] !== undefined + ? strategiesAcumScore + strategyScores[delegatorAddress] + : strategiesAcumScore; + }, 0); +} + +export function getDelegateScore( + delegations: string[], + scores: Record[] +) { + return delegations.reduce( + (delegationsAcumScore, delegatorAddress) => + delegationsAcumScore + getDelegatorScores(scores, delegatorAddress), + 0 + ); +} diff --git a/test/mergeDelegations.test.ts b/test/mergeDelegations.test.ts index 7f96b74b8..4b3c64f50 100644 --- a/test/mergeDelegations.test.ts +++ b/test/mergeDelegations.test.ts @@ -109,7 +109,6 @@ describe('when legacyDelegations or multiDelegations is null or undefined', () = }); }); -// Test when a multiDelegation has an empty array and has a common key with legacyDelegations describe('when a multiDelegation has an empty array and has a common key with legacyDelegations', () => { it('returns merged delegations', () => { const legacyDelegations = new Map([ @@ -134,7 +133,6 @@ describe('when a multiDelegation has an empty array and has a common key with le }); }); -// Test when a multiDelegation has an empty array and has no common key with legacyDelegations describe('when a multiDelegation has an empty array and has no common key with legacyDelegations', () => { it('returns merged delegations', () => { const legacyDelegations = new Map([ diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index b9e8996bc..3c3703f44 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -133,7 +133,7 @@ const SNAPSHOT = 'snapshot1'; // } test('pruebita', async () => { - const SNAPSHOT = 17316799; + const SNAPSHOT = 'latest'; const NETWORK = '1'; const PROVIDER = snapshot.utils.getProvider(NETWORK); const SPACE = '1emu.eth'; From ca04c5f37fa225095c230efc8acf6f7633a4b69a Mon Sep 17 00:00:00 2001 From: ncomerci Date: Mon, 29 May 2023 14:11:33 -0300 Subject: [PATCH 10/23] fix: empty delegations --- src/strategies/multidelegation/index.ts | 7 +++++-- src/strategies/multidelegation/utils.ts | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index 8d4dec6a0..094e3e4c3 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -40,7 +40,6 @@ export async function strategy( const multiDelegationsPromise = getMultiDelegations( delegationSpace, network, - checksummedAddresses, polygonBlockNumber ); @@ -54,7 +53,11 @@ export async function strategy( const isLegacyDelegationEmpty = legacyDelegations.size === 0; const isMultiDelegationEmpty = multiDelegations.size === 0; - if (isLegacyDelegationEmpty && isMultiDelegationEmpty) return {}; + if (isLegacyDelegationEmpty && isMultiDelegationEmpty) { + return Object.fromEntries( + addresses.map((address) => [getAddress(address), 0]) + ); + } const mergedDelegations = mergeDelegations( legacyDelegations, diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index 94967c381..c30067349 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -49,7 +49,6 @@ export async function getPolygonDelegatesBySpace( export async function getMultiDelegations( space: string, network: string, - addresses: string[], snapshot?: string ): Promise> { const delegatesBySpace = await getPolygonDelegatesBySpace( From 8d40cdb4f7e1e8a2352f911feb23fd94593f18f8 Mon Sep 17 00:00:00 2001 From: lemu Date: Mon, 29 May 2023 16:11:52 -0300 Subject: [PATCH 11/23] chore: multidelegation strategy mocks and tests --- src/strategies/multidelegation/examples.json | 7 +- src/strategies/multidelegation/index.ts | 14 +- test/multidelegation.test.ts | 167 ++++++++++++++----- 3 files changed, 129 insertions(+), 59 deletions(-) diff --git a/src/strategies/multidelegation/examples.json b/src/strategies/multidelegation/examples.json index 1b1e284bf..ac79e8d75 100644 --- a/src/strategies/multidelegation/examples.json +++ b/src/strategies/multidelegation/examples.json @@ -110,9 +110,10 @@ "0x6cd7694d30c10bdab1e644fc1964043a95ceea5f", "0x549a9021661a85b6bc51c07b3a451135848d0048", "0x30b1f4bd5476906f38385b891f2c09973196b742", - "0x511a22cdd2c4ee8357bb02df2578037ffe8a4d8d", - "0xb0f847e61c502fb82d758c515b3f914de42831d5" + "0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d", + "0xb0F847e61C502Fb82D758C515b3F914de42831D5", + "0xcD15d83f42179b9A5B515eea0975f554444a9646" ], "snapshot": 17331246 } -] \ No newline at end of file +] diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index 094e3e4c3..408c2a950 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -22,6 +22,7 @@ export async function strategy( ) { const delegationSpace = options.delegationSpace || space; const checksummedAddresses = addresses.map(getAddress); + // Retro compatibility with the legacy delegation strategy const legacyDelegationsPromise = getLegacyDelegations( 'snapshot.dcl.eth', @@ -34,7 +35,6 @@ export async function strategy( const blocks = await getSnapshots(network, snapshot, provider, [ polygonChainId ]); - const polygonBlockNumber = blocks[polygonChainId]; const multiDelegationsPromise = getMultiDelegations( @@ -47,15 +47,13 @@ export async function strategy( legacyDelegationsPromise, multiDelegationsPromise ]); - console.log('legacyDelegations', legacyDelegations); - console.log('multiDelegations', multiDelegations); const isLegacyDelegationEmpty = legacyDelegations.size === 0; const isMultiDelegationEmpty = multiDelegations.size === 0; if (isLegacyDelegationEmpty && isMultiDelegationEmpty) { return Object.fromEntries( - addresses.map((address) => [getAddress(address), 0]) + checksummedAddresses.map((address) => [address, 0]) ); } @@ -63,11 +61,7 @@ export async function strategy( legacyDelegations, multiDelegations ); - - console.log('mergedDelegations', mergedDelegations); - const reversedDelegations = reverseDelegations(mergedDelegations); - console.log('reversedDelegations', reversedDelegations); const delegationAddresses = Array.from(reversedDelegations.values()).reduce( (accumulator, addresses) => accumulator.concat(addresses), @@ -85,10 +79,8 @@ export async function strategy( ) ).filter((score) => Object.keys(score).length !== 0); - console.log('scores', scores); - return Object.fromEntries( - addresses.map((delegate) => { + checksummedAddresses.map((delegate) => { const delegations = reversedDelegations.get(delegate); const delegateScore = delegations ? getDelegateScore(delegations, scores) diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index 3c3703f44..2621726da 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -1,13 +1,14 @@ // @ts-nocheck - -import { strategy } from '../src/strategies/multidelegation/index'; -// import { getScoresDirect } from '../src/utils'; +import { strategy } from '../src/strategies/multidelegation'; +import * as utils from '../src/utils'; +import * as multidelegationUtils from '../src/strategies/multidelegation/utils'; import snapshot from '../src'; +import { getAddress } from '@ethersproject/address'; -const SPACE = 'space1'; +const SNAPSHOT = 'latest'; const NETWORK = '1'; -const PROVIDER = 'provider1'; -const ADDRESSES = ['address1', 'address2']; +const PROVIDER = snapshot.utils.getProvider(NETWORK); +const SPACE = '1emu.eth'; const OPTIONS = { strategies: [ { @@ -114,47 +115,123 @@ const OPTIONS = { } ] }; -const SNAPSHOT = 'snapshot1'; -// -// function mockLegacyDelegation(result: { [k: string]: any }) { -// return jest -// .spyOn(legacyDelegationStrategy, 'strategy') -// .mockResolvedValue(result); -// } -// -// function mockGetMultiDelegations(result: { [k: string]: any }) { -// return jest -// .spyOn(multidelegationUtils, 'getMultiDelegations') -// .mockResolvedValue(result); -// } -// -// function mockGetScoresDirect(result: Record[]) { -// return jest.spyOn(utils, 'getScoresDirect').mockResolvedValue(result); -// } +const SCORE_PER_STRATEGY = 10; +const DELEGATOR_SCORE = OPTIONS.strategies.length * SCORE_PER_STRATEGY; + +function mockGetLegacyDelegations(result: string[][]) { + return jest + .spyOn(multidelegationUtils, 'getLegacyDelegations') + .mockResolvedValue( + new Map(result.map((array) => [getAddress(array[0]), array[1]])) + ); +} + +function mockGetMultiDelegations(result: string[][]) { + return jest + .spyOn(multidelegationUtils, 'getMultiDelegations') + .mockResolvedValue( + new Map(result.map((array) => [getAddress(array[0]), array[1]])) + ); +} + +function mockGetScoresDirect() { + return jest + .spyOn(utils, 'getScoresDirect') + .mockImplementation( + ( + space: string, + strategies: any[], + network: string, + provider, + addresses: string[] + ) => + strategies.map(() => { + return Object.fromEntries( + addresses.map((address) => [address, SCORE_PER_STRATEGY]) + ); + }) + ); +} + +const ADDRESS_N = '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F'; +const ADDRESS_L = '0x549A9021661a85B6BC51c07B3A451135848d0048'; +const ADDRESS_G = '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d'; +const ADDRESS_X = '0x30b1f4Bd5476906f38385B891f2c09973196b742'; +const ADDRESS_Y = '0x0f051A642A1c4B2c268C7D6a83186159b149021b'; +const ADDRESS_A = '0xb0F847e61C502Fb82D758C515b3F914de42831D5'; +const ADDRESS_GS = '0xBf363AeDd082Ddd8DB2D6457609B03f9ee74a2F1'; +const ADDRESS_Z = '0x76DA87b314aa6878d06344eE14fcd1bBB7E8FDb5'; + +describe('multidelegation', () => { + const ADDRESSES = [ADDRESS_N, ADDRESS_L, ADDRESS_Y, ADDRESS_G, ADDRESS_A]; + beforeEach(() => mockGetScoresDirect()); + + describe('when there are some legacy delegations', () => { + beforeEach(() => { + mockGetLegacyDelegations([ + [ADDRESS_N, ADDRESS_L], + [ADDRESS_L, ADDRESS_G], + [ADDRESS_X, ADDRESS_Y], + [ADDRESS_A, ADDRESS_G] + ]); + }); + + describe('when there are some multi delegations overriding legacy delegations', () => { + beforeEach(() => { + mockGetMultiDelegations([ + [ADDRESS_L, [ADDRESS_A]], + [ADDRESS_Z, [ADDRESS_L, ADDRESS_N]], + [ADDRESS_GS, [ADDRESS_L]] + ]); + }); + + it('returns a score for each received address', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); + + expect(Object.keys(result).length).toEqual(ADDRESSES.length); + }); + + it('returns the delegated score for each address', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); -test('pruebita', async () => { - const SNAPSHOT = 'latest'; - const NETWORK = '1'; - const PROVIDER = snapshot.utils.getProvider(NETWORK); - const SPACE = '1emu.eth'; - const ADDRESSES = [ - '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F', - '0x549A9021661a85B6BC51c07B3A451135848d0048', - '0x30b1f4bd5476906f38385b891f2c09973196b742', - '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d', - '0xb0F847e61C502Fb82D758C515b3F914de42831D5' - ]; + expect(result[ADDRESS_L]).toEqual(DELEGATOR_SCORE * 3); + expect(result[ADDRESS_N]).toEqual(DELEGATOR_SCORE); + expect(result[ADDRESS_A]).toEqual(DELEGATOR_SCORE); + expect(result[ADDRESS_Y]).toEqual(DELEGATOR_SCORE); + expect(result[ADDRESS_G]).toEqual(DELEGATOR_SCORE); + }); - // [{address1: vp, address2: score}, {address1: 0}] + describe('when some of the input addresses are not checksummed', () => { + const ADDRESS_LOWERCASE = [ADDRESS_L.toLowerCase()]; - const result = await strategy( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); + it('should return the same calculated amount for the checksum address', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + ADDRESS_LOWERCASE, + OPTIONS, + SNAPSHOT + ); - console.log('result', JSON.stringify(result)); + expect(result[ADDRESS_L]).toEqual(DELEGATOR_SCORE * 3); + expect(result[ADDRESS_LOWERCASE]).toBeUndefined(); + }); + }); + }); + }); }); From 0bb4553f94f8015e85d821ef91dde961cbae2050 Mon Sep 17 00:00:00 2001 From: lemu Date: Mon, 29 May 2023 16:54:44 -0300 Subject: [PATCH 12/23] chore: tests and refactors --- src/strategies/multidelegation/index.ts | 37 ++++++-------- src/strategies/multidelegation/utils.ts | 57 ++++++++++++++++++--- test/multidelegation.test.ts | 67 ++++++++++++++++++++----- 3 files changed, 119 insertions(+), 42 deletions(-) diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index 408c2a950..5f0738902 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -1,11 +1,12 @@ import { - getDelegateScore, + getAddressTotalDelegatedScore, + getDelegationAddresses, getLegacyDelegations, - getMultiDelegations, + getPolygonMultiDelegations, mergeDelegations, reverseDelegations } from './utils'; -import { getScoresDirect, getSnapshots } from '../../utils'; +import { getScoresDirect } from '../../utils'; import { getAddress } from '@ethersproject/address'; export const author = 'dcl-DAO'; @@ -30,17 +31,11 @@ export async function strategy( checksummedAddresses, snapshot ); - - const polygonChainId = '80001'; - const blocks = await getSnapshots(network, snapshot, provider, [ - polygonChainId - ]); - const polygonBlockNumber = blocks[polygonChainId]; - - const multiDelegationsPromise = getMultiDelegations( - delegationSpace, + const multiDelegationsPromise = getPolygonMultiDelegations( network, - polygonBlockNumber + snapshot, + provider, + delegationSpace ); const [legacyDelegations, multiDelegations] = await Promise.all([ @@ -62,11 +57,7 @@ export async function strategy( multiDelegations ); const reversedDelegations = reverseDelegations(mergedDelegations); - - const delegationAddresses = Array.from(reversedDelegations.values()).reduce( - (accumulator, addresses) => accumulator.concat(addresses), - [] - ); + const delegationAddresses = getDelegationAddresses(reversedDelegations); const scores = ( await getScoresDirect( @@ -81,11 +72,11 @@ export async function strategy( return Object.fromEntries( checksummedAddresses.map((delegate) => { - const delegations = reversedDelegations.get(delegate); - const delegateScore = delegations - ? getDelegateScore(delegations, scores) - : 0; - return [delegate, delegateScore]; + return getAddressTotalDelegatedScore( + delegate, + reversedDelegations, + scores + ); }) ); } diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index c30067349..27ac3d6a5 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -1,8 +1,11 @@ import { getAddress } from '@ethersproject/address'; -import { getDelegatesBySpace, subgraphRequest } from '../../utils'; +import { + getDelegatesBySpace, + getSnapshots, + subgraphRequest +} from '../../utils'; export async function getPolygonDelegatesBySpace( - network: string, space: string, snapshot = 'latest' ) { @@ -51,11 +54,7 @@ export async function getMultiDelegations( network: string, snapshot?: string ): Promise> { - const delegatesBySpace = await getPolygonDelegatesBySpace( - network, - space, - snapshot - ); + const delegatesBySpace = await getPolygonDelegatesBySpace(space, snapshot); return delegatesBySpace.reduce((accum, delegation) => { const delegator = getAddress(delegation.delegator); @@ -166,3 +165,47 @@ export function getDelegateScore( 0 ); } + +export function getAddressTotalDelegatedScore( + delegate, + reversedDelegations: Map, + scores +) { + const delegations = reversedDelegations.get(delegate); + const delegateScore = delegations ? getDelegateScore(delegations, scores) : 0; + return [delegate, delegateScore]; +} + +export async function getPolygonBlockNumber(network, snapshot, provider) { + const polygonChainId = '80001'; + const blocks = await getSnapshots(network, snapshot, provider, [ + polygonChainId + ]); + return blocks[polygonChainId]; +} + +export async function getPolygonMultiDelegations( + network, + snapshot, + provider, + delegationSpace +) { + const polygonBlockNumber = await getPolygonBlockNumber( + network, + snapshot, + provider + ); + + return getMultiDelegations(delegationSpace, network, polygonBlockNumber); +} + +export function getDelegationAddresses( + reversedDelegations: Map +) { + return Array.from( + Array.from(reversedDelegations.values()).reduce( + (accumulator, addresses) => new Set([...accumulator, ...addresses]), + new Set() + ) + ); +} diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index 2621726da..941ec91b5 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -1,7 +1,7 @@ -// @ts-nocheck import { strategy } from '../src/strategies/multidelegation'; import * as utils from '../src/utils'; -import * as multidelegationUtils from '../src/strategies/multidelegation/utils'; +import * as multiDelegationUtils from '../src/strategies/multidelegation/utils'; +import { getDelegationAddresses } from '../src/strategies/multidelegation/utils'; import snapshot from '../src'; import { getAddress } from '@ethersproject/address'; @@ -120,15 +120,15 @@ const DELEGATOR_SCORE = OPTIONS.strategies.length * SCORE_PER_STRATEGY; function mockGetLegacyDelegations(result: string[][]) { return jest - .spyOn(multidelegationUtils, 'getLegacyDelegations') + .spyOn(multiDelegationUtils, 'getLegacyDelegations') .mockResolvedValue( new Map(result.map((array) => [getAddress(array[0]), array[1]])) ); } -function mockGetMultiDelegations(result: string[][]) { +function mockGetMultiDelegations(result: [string, string[]][]) { return jest - .spyOn(multidelegationUtils, 'getMultiDelegations') + .spyOn(multiDelegationUtils, 'getMultiDelegations') .mockResolvedValue( new Map(result.map((array) => [getAddress(array[0]), array[1]])) ); @@ -145,11 +145,13 @@ function mockGetScoresDirect() { provider, addresses: string[] ) => - strategies.map(() => { - return Object.fromEntries( - addresses.map((address) => [address, SCORE_PER_STRATEGY]) - ); - }) + Promise.resolve( + strategies.map(() => { + return Object.fromEntries( + addresses.map((address) => [address, SCORE_PER_STRATEGY]) + ); + }) + ) ); } @@ -216,14 +218,14 @@ describe('multidelegation', () => { }); describe('when some of the input addresses are not checksummed', () => { - const ADDRESS_LOWERCASE = [ADDRESS_L.toLowerCase()]; + const ADDRESS_LOWERCASE = ADDRESS_L.toLowerCase(); it('should return the same calculated amount for the checksum address', async () => { const result = await strategy( SPACE, NETWORK, PROVIDER, - ADDRESS_LOWERCASE, + [ADDRESS_LOWERCASE], OPTIONS, SNAPSHOT ); @@ -235,3 +237,44 @@ describe('multidelegation', () => { }); }); }); + +describe('getDelegationAddresses', () => { + describe('when it receives a list of addresses with repeated delegations', () => { + const reversedDelegations = new Map([ + [ + '0x549A9021661a85B6BC51c07B3A451135848d0048', + [ + '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F', + '0x76DA87b314aa6878d06344eE14fcd1bBB7E8FDb5', + '0xBf363AeDd082Ddd8DB2D6457609B03f9ee74a2F1' + ] + ], + [ + '0xb0F847e61C502Fb82D758C515b3F914de42831D5', + ['0x549A9021661a85B6BC51c07B3A451135848d0048'] + ], + [ + '0x0f051A642A1c4B2c268C7D6a83186159b149021b', + ['0x30b1f4Bd5476906f38385B891f2c09973196b742'] + ], + [ + '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d', + ['0xb0F847e61C502Fb82D758C515b3F914de42831D5'] + ], + [ + '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F', + ['0x76DA87b314aa6878d06344eE14fcd1bBB7E8FDb5'] + ] + ]); + it('does not include repeated addresses', () => { + expect(getDelegationAddresses(reversedDelegations)).toEqual([ + '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F', + '0x76DA87b314aa6878d06344eE14fcd1bBB7E8FDb5', + '0xBf363AeDd082Ddd8DB2D6457609B03f9ee74a2F1', + '0x549A9021661a85B6BC51c07B3A451135848d0048', + '0x30b1f4Bd5476906f38385B891f2c09973196b742', + '0xb0F847e61C502Fb82D758C515b3F914de42831D5' + ]); + }); + }); +}); From 204fb3377e65824bcc243722c884ed29b0845c86 Mon Sep 17 00:00:00 2001 From: lemu Date: Mon, 29 May 2023 17:05:09 -0300 Subject: [PATCH 13/23] chore: use received space in strategy --- src/strategies/multidelegation/index.ts | 2 +- test/multidelegation.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index 5f0738902..a4c7a0651 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -26,7 +26,7 @@ export async function strategy( // Retro compatibility with the legacy delegation strategy const legacyDelegationsPromise = getLegacyDelegations( - 'snapshot.dcl.eth', + delegationSpace, network, checksummedAddresses, snapshot diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index 941ec91b5..275aed18e 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -8,7 +8,7 @@ import { getAddress } from '@ethersproject/address'; const SNAPSHOT = 'latest'; const NETWORK = '1'; const PROVIDER = snapshot.utils.getProvider(NETWORK); -const SPACE = '1emu.eth'; +const SPACE = 'some.space.eth'; const OPTIONS = { strategies: [ { From 548fa06c0b4de88bf2d2a8dfd66a76d9d64b85a7 Mon Sep 17 00:00:00 2001 From: lemu Date: Tue, 30 May 2023 15:35:43 -0300 Subject: [PATCH 14/23] chore: add some more multidelegation test cases --- test/multidelegation.test.ts | 207 ++++++++++++++++++++++++++--------- 1 file changed, 158 insertions(+), 49 deletions(-) diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index 275aed18e..f4e1a85a5 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -128,7 +128,7 @@ function mockGetLegacyDelegations(result: string[][]) { function mockGetMultiDelegations(result: [string, string[]][]) { return jest - .spyOn(multiDelegationUtils, 'getMultiDelegations') + .spyOn(multiDelegationUtils, 'getPolygonMultiDelegations') .mockResolvedValue( new Map(result.map((array) => [getAddress(array[0]), array[1]])) ); @@ -155,6 +155,24 @@ function mockGetScoresDirect() { ); } +function mockGetScoresDirectNoWMANA() { + return jest.spyOn(utils, 'getScoresDirect').mockResolvedValue([ + {}, + { '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d': 2000 }, + {}, + { + '0x6cd7694d30c10bdab1e644fc1964043a95ceea5f': 100, + '0x549a9021661a85b6bc51c07b3a451135848d0048': 0, + '0x30b1f4bd5476906f38385b891f2c09973196b742': 127.5, + '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d': 120, + '0xb0F847e61C502Fb82D758C515b3F914de42831D5': 50 + }, + {}, + {}, + {} + ]); +} + const ADDRESS_N = '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F'; const ADDRESS_L = '0x549A9021661a85B6BC51c07B3A451135848d0048'; const ADDRESS_G = '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d'; @@ -166,76 +184,167 @@ const ADDRESS_Z = '0x76DA87b314aa6878d06344eE14fcd1bBB7E8FDb5'; describe('multidelegation', () => { const ADDRESSES = [ADDRESS_N, ADDRESS_L, ADDRESS_Y, ADDRESS_G, ADDRESS_A]; - beforeEach(() => mockGetScoresDirect()); - describe('when there are some legacy delegations', () => { - beforeEach(() => { - mockGetLegacyDelegations([ - [ADDRESS_N, ADDRESS_L], - [ADDRESS_L, ADDRESS_G], - [ADDRESS_X, ADDRESS_Y], - [ADDRESS_A, ADDRESS_G] - ]); - }); + describe('when every address has a score of 10 for every strategy', () => { + beforeEach(() => mockGetScoresDirect()); - describe('when there are some multi delegations overriding legacy delegations', () => { + describe('when there are some legacy delegations', () => { beforeEach(() => { - mockGetMultiDelegations([ - [ADDRESS_L, [ADDRESS_A]], - [ADDRESS_Z, [ADDRESS_L, ADDRESS_N]], - [ADDRESS_GS, [ADDRESS_L]] + mockGetLegacyDelegations([ + [ADDRESS_N, ADDRESS_L], + [ADDRESS_L, ADDRESS_G], + [ADDRESS_X, ADDRESS_Y], + [ADDRESS_A, ADDRESS_G] ]); }); - it('returns a score for each received address', async () => { - const result = await strategy( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); - - expect(Object.keys(result).length).toEqual(ADDRESSES.length); - }); - - it('returns the delegated score for each address', async () => { - const result = await strategy( - SPACE, - NETWORK, - PROVIDER, - ADDRESSES, - OPTIONS, - SNAPSHOT - ); + describe('when there are some multi delegations overriding legacy delegations', () => { + beforeEach(() => { + mockGetMultiDelegations([ + [ADDRESS_L, [ADDRESS_A]], + [ADDRESS_Z, [ADDRESS_L, ADDRESS_N]], + [ADDRESS_GS, [ADDRESS_L]] + ]); + }); - expect(result[ADDRESS_L]).toEqual(DELEGATOR_SCORE * 3); - expect(result[ADDRESS_N]).toEqual(DELEGATOR_SCORE); - expect(result[ADDRESS_A]).toEqual(DELEGATOR_SCORE); - expect(result[ADDRESS_Y]).toEqual(DELEGATOR_SCORE); - expect(result[ADDRESS_G]).toEqual(DELEGATOR_SCORE); - }); + it('returns a score for each received address', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + OPTIONS, + SNAPSHOT + ); - describe('when some of the input addresses are not checksummed', () => { - const ADDRESS_LOWERCASE = ADDRESS_L.toLowerCase(); + expect(Object.keys(result).length).toEqual(ADDRESSES.length); + }); - it('should return the same calculated amount for the checksum address', async () => { + it('returns the delegated score for each address', async () => { const result = await strategy( SPACE, NETWORK, PROVIDER, - [ADDRESS_LOWERCASE], + ADDRESSES, OPTIONS, SNAPSHOT ); expect(result[ADDRESS_L]).toEqual(DELEGATOR_SCORE * 3); - expect(result[ADDRESS_LOWERCASE]).toBeUndefined(); + expect(result[ADDRESS_N]).toEqual(DELEGATOR_SCORE); + expect(result[ADDRESS_A]).toEqual(DELEGATOR_SCORE); + expect(result[ADDRESS_Y]).toEqual(DELEGATOR_SCORE); + expect(result[ADDRESS_G]).toEqual(DELEGATOR_SCORE); + }); + + describe('when some of the input addresses are not checksummed', () => { + const ADDRESS_LOWERCASE = ADDRESS_L.toLowerCase(); + + it('should return the same calculated amount for the checksum address', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + [ADDRESS_LOWERCASE], + OPTIONS, + SNAPSHOT + ); + + expect(result[ADDRESS_L]).toEqual(DELEGATOR_SCORE * 3); + expect(result[ADDRESS_LOWERCASE]).toBeUndefined(); + }); }); }); }); }); + + describe('when there is no scores for any address for a particular strategy', () => { + beforeEach(() => { + mockGetScoresDirectNoWMANA(); + mockGetLegacyDelegations([[ADDRESS_G, ADDRESS_L]]); + mockGetMultiDelegations([[ADDRESS_G, [ADDRESS_N]]]); + }); + + it('should not throw', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + [ADDRESS_L, ADDRESS_N], + OPTIONS, + SNAPSHOT + ); + + expect(result[ADDRESS_L]).toEqual(0); + expect(result[ADDRESS_N]).toEqual(2120); + }); + }); + + describe('when there are no legacy delegations', () => { + beforeEach(() => { + mockGetScoresDirectNoWMANA(); + mockGetLegacyDelegations([]); + mockGetMultiDelegations([[ADDRESS_G, [ADDRESS_N]]]); + }); + + it('should not throw', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + [ADDRESS_L, ADDRESS_N], + OPTIONS, + SNAPSHOT + ); + + expect(result[ADDRESS_N]).toEqual(2120); + expect(result[ADDRESS_L]).toEqual(0); + }); + }); + + describe('when there are only legacy delegations', () => { + beforeEach(() => { + mockGetScoresDirectNoWMANA(); + mockGetLegacyDelegations([[ADDRESS_G, ADDRESS_L]]); + mockGetMultiDelegations([]); + }); + + it('should not throw', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + [ADDRESS_L, ADDRESS_N], + OPTIONS, + SNAPSHOT + ); + + expect(result[ADDRESS_L]).toEqual(2120); + expect(result[ADDRESS_N]).toEqual(0); + }); + }); + + describe('when there are no delegations', () => { + beforeEach(() => { + mockGetScoresDirectNoWMANA(); + mockGetLegacyDelegations([]); + mockGetMultiDelegations([]); + }); + + it('should not throw', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + [ADDRESS_L, ADDRESS_N], + OPTIONS, + SNAPSHOT + ); + + expect(result[ADDRESS_L]).toEqual(0); + expect(result[ADDRESS_N]).toEqual(0); + }); + }); }); describe('getDelegationAddresses', () => { From ab580ef43ce71a38988e57e7a7db2f9073721726 Mon Sep 17 00:00:00 2001 From: lemu Date: Tue, 30 May 2023 16:05:35 -0300 Subject: [PATCH 15/23] chore: use pre-generated addresses in tests and examples --- src/strategies/multidelegation/examples.json | 12 ++-- src/testScript.ts | 12 ---- test/multidelegation.test.ts | 63 ++++++++++---------- 3 files changed, 37 insertions(+), 50 deletions(-) delete mode 100644 src/testScript.ts diff --git a/src/strategies/multidelegation/examples.json b/src/strategies/multidelegation/examples.json index ac79e8d75..16258bb2b 100644 --- a/src/strategies/multidelegation/examples.json +++ b/src/strategies/multidelegation/examples.json @@ -107,12 +107,12 @@ }, "network": "1", "addresses": [ - "0x6cd7694d30c10bdab1e644fc1964043a95ceea5f", - "0x549a9021661a85b6bc51c07b3a451135848d0048", - "0x30b1f4bd5476906f38385b891f2c09973196b742", - "0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d", - "0xb0F847e61C502Fb82D758C515b3F914de42831D5", - "0xcD15d83f42179b9A5B515eea0975f554444a9646" + "0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7", + "0x49E4DbfF86a2E5DA27c540c9A9E8D2C3726E278F", + "0xd7539FCdC0aB79a7B688b04387cb128E75cb77Dc", + "0x4757cE43Dc5429B8F1A132DC29eF970E55Ae722B", + "0xC9dA7343583fA8Bb380A6F04A208C612F86C7701", + "0x69ABF813a683391C0ec888351912E14590B56e88" ], "snapshot": 17331246 } diff --git a/src/testScript.ts b/src/testScript.ts deleted file mode 100644 index 63f8cea1b..000000000 --- a/src/testScript.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { getMultiDelegations } from './strategies/multidelegation/utils'; - -async function fn() { - console.log( - await getMultiDelegations('1emu.eth', '1', [ - '0x549a9021661a85b6bc51c07b3a451135848D0048', - '0xbf363aedd082ddd8db2d6457609b03f9ee74A2f1' - ]) - ); -} - -fn(); diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index f4e1a85a5..ab12d8a6b 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -117,6 +117,14 @@ const OPTIONS = { }; const SCORE_PER_STRATEGY = 10; const DELEGATOR_SCORE = OPTIONS.strategies.length * SCORE_PER_STRATEGY; +const ADDRESS_N = '0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7'; +const ADDRESS_L = '0x49E4DbfF86a2E5DA27c540c9A9E8D2C3726E278F'; +const ADDRESS_G = '0x4757cE43Dc5429B8F1A132DC29eF970E55Ae722B'; +const ADDRESS_X = '0xd7539FCdC0aB79a7B688b04387cb128E75cb77Dc'; +const ADDRESS_Y = '0x6E33e22f7aC5A4b58A93C7f6D8Da8b46c50A3E20'; +const ADDRESS_A = '0xC9dA7343583fA8Bb380A6F04A208C612F86C7701'; +const ADDRESS_GS = '0x2AC89522CB415AC333E64F52a1a5693218cEBD58'; +const ADDRESS_Z = '0xd90c6f6D37716b1Cc4dd2B116be42e8683550F45'; function mockGetLegacyDelegations(result: string[][]) { return jest @@ -158,14 +166,14 @@ function mockGetScoresDirect() { function mockGetScoresDirectNoWMANA() { return jest.spyOn(utils, 'getScoresDirect').mockResolvedValue([ {}, - { '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d': 2000 }, + { '0x4757cE43Dc5429B8F1A132DC29eF970E55Ae722B': 2000 }, {}, { - '0x6cd7694d30c10bdab1e644fc1964043a95ceea5f': 100, + '0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7': 100, '0x549a9021661a85b6bc51c07b3a451135848d0048': 0, - '0x30b1f4bd5476906f38385b891f2c09973196b742': 127.5, - '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d': 120, - '0xb0F847e61C502Fb82D758C515b3F914de42831D5': 50 + '0xd7539FCdC0aB79a7B688b04387cb128E75cb77Dc': 127.5, + '0x4757cE43Dc5429B8F1A132DC29eF970E55Ae722B': 120, + '0xC9dA7343583fA8Bb380A6F04A208C612F86C7701': 50 }, {}, {}, @@ -173,15 +181,6 @@ function mockGetScoresDirectNoWMANA() { ]); } -const ADDRESS_N = '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F'; -const ADDRESS_L = '0x549A9021661a85B6BC51c07B3A451135848d0048'; -const ADDRESS_G = '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d'; -const ADDRESS_X = '0x30b1f4Bd5476906f38385B891f2c09973196b742'; -const ADDRESS_Y = '0x0f051A642A1c4B2c268C7D6a83186159b149021b'; -const ADDRESS_A = '0xb0F847e61C502Fb82D758C515b3F914de42831D5'; -const ADDRESS_GS = '0xBf363AeDd082Ddd8DB2D6457609B03f9ee74a2F1'; -const ADDRESS_Z = '0x76DA87b314aa6878d06344eE14fcd1bBB7E8FDb5'; - describe('multidelegation', () => { const ADDRESSES = [ADDRESS_N, ADDRESS_L, ADDRESS_Y, ADDRESS_G, ADDRESS_A]; @@ -351,38 +350,38 @@ describe('getDelegationAddresses', () => { describe('when it receives a list of addresses with repeated delegations', () => { const reversedDelegations = new Map([ [ - '0x549A9021661a85B6BC51c07B3A451135848d0048', + '0x49E4DbfF86a2E5DA27c540c9A9E8D2C3726E278F', [ - '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F', - '0x76DA87b314aa6878d06344eE14fcd1bBB7E8FDb5', - '0xBf363AeDd082Ddd8DB2D6457609B03f9ee74a2F1' + '0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7', + '0xd90c6f6D37716b1Cc4dd2B116be42e8683550F45', + '0x2AC89522CB415AC333E64F52a1a5693218cEBD58' ] ], [ - '0xb0F847e61C502Fb82D758C515b3F914de42831D5', - ['0x549A9021661a85B6BC51c07B3A451135848d0048'] + '0xC9dA7343583fA8Bb380A6F04A208C612F86C7701', + ['0x49E4DbfF86a2E5DA27c540c9A9E8D2C3726E278F'] ], [ - '0x0f051A642A1c4B2c268C7D6a83186159b149021b', - ['0x30b1f4Bd5476906f38385B891f2c09973196b742'] + '0x6E33e22f7aC5A4b58A93C7f6D8Da8b46c50A3E20', + ['0xd7539FCdC0aB79a7B688b04387cb128E75cb77Dc'] ], [ - '0x511a22cDd2c4eE8357bB02df2578037Ffe8a4d8d', - ['0xb0F847e61C502Fb82D758C515b3F914de42831D5'] + '0x4757cE43Dc5429B8F1A132DC29eF970E55Ae722B', + ['0xC9dA7343583fA8Bb380A6F04A208C612F86C7701'] ], [ - '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F', - ['0x76DA87b314aa6878d06344eE14fcd1bBB7E8FDb5'] + '0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7', + ['0xd90c6f6D37716b1Cc4dd2B116be42e8683550F45'] ] ]); it('does not include repeated addresses', () => { expect(getDelegationAddresses(reversedDelegations)).toEqual([ - '0x6Cd7694d30c10bdAB1E644FC1964043a95cEEa5F', - '0x76DA87b314aa6878d06344eE14fcd1bBB7E8FDb5', - '0xBf363AeDd082Ddd8DB2D6457609B03f9ee74a2F1', - '0x549A9021661a85B6BC51c07B3A451135848d0048', - '0x30b1f4Bd5476906f38385B891f2c09973196b742', - '0xb0F847e61C502Fb82D758C515b3F914de42831D5' + '0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7', + '0xd90c6f6D37716b1Cc4dd2B116be42e8683550F45', + '0x2AC89522CB415AC333E64F52a1a5693218cEBD58', + '0x49E4DbfF86a2E5DA27c540c9A9E8D2C3726E278F', + '0xd7539FCdC0aB79a7B688b04387cb128E75cb77Dc', + '0xC9dA7343583fA8Bb380A6F04A208C612F86C7701' ]); }); }); From de6bc79ba0695fdbb8ed2ef9432ec2792aec787e Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 30 May 2023 16:34:37 -0300 Subject: [PATCH 16/23] changed author --- src/strategies/multidelegation/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index a4c7a0651..d41486300 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -9,7 +9,7 @@ import { import { getScoresDirect } from '../../utils'; import { getAddress } from '@ethersproject/address'; -export const author = 'dcl-DAO'; +export const author = 'ncomerci'; export const version = '0.1.0'; export const dependOnOtherAddress = true; From 9c5dcc4dc281ddba269279f54deb578ed47c5440 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 30 May 2023 17:09:43 -0300 Subject: [PATCH 17/23] added Readme --- src/strategies/multidelegation/README.md | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/strategies/multidelegation/README.md diff --git a/src/strategies/multidelegation/README.md b/src/strategies/multidelegation/README.md new file mode 100644 index 000000000..02270fb77 --- /dev/null +++ b/src/strategies/multidelegation/README.md @@ -0,0 +1,39 @@ +# multidelegation + +If you want to delegate your voting power to multiple wallet addresses, you can do this using the “multidelegation strategy”. This strategy is based on [delegation strategy](https://github.com/snapshot-labs/snapshot-strategies/tree/master/src/strategies/delegation) with the exeption that you can delegate to several addresses at the same time. + +If A delegates to B and C, A's score is splitted equaly to B and C. In case A already has a delegation made with the [delegation strategy](https://github.com/snapshot-labs/snapshot-strategies/tree/master/src/strategies/delegation), this “multidelegation strategy” overrides it. + +The multidelegation smart contract is in Polygon, so the gas fee to delegate is way lower. + +| Param Name | Description | +| ----------- | ----------- | +| strategies | list of sub strategies to calculate voting power based on delegation | +| delegationSpace (optional) | Get delegations of a particular space (by default it take delegations of current space) | + +Here is an example of parameters: + +```json +{ + "symbol": "YFI (delegated)", + "strategies": [ + { + "name": "erc20-balance-of", + "params": { + "address": "0xBa37B002AbaFDd8E89a1995dA52740bbC013D992", + "symbol": "YFI", + "decimals": 18 + } + }, + { + "name": "yearn-vault", + "params": { + "address": "0xBA2E7Fed597fd0E3e70f5130BcDbbFE06bB94fe1", + "symbol": "YFI (yYFI)", + "decimals": 18 + } + } + ] +} + +``` \ No newline at end of file From f1912ec2d1adf5c1f02b30c544055831fcd68897 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Wed, 31 May 2023 12:13:04 -0300 Subject: [PATCH 18/23] fix: score splitting in multidelegation --- src/strategies/multidelegation/README.md | 4 +-- src/strategies/multidelegation/index.ts | 1 + src/strategies/multidelegation/utils.ts | 21 +++++++---- test/multidelegation.test.ts | 46 ++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/src/strategies/multidelegation/README.md b/src/strategies/multidelegation/README.md index 02270fb77..952d34191 100644 --- a/src/strategies/multidelegation/README.md +++ b/src/strategies/multidelegation/README.md @@ -1,8 +1,8 @@ # multidelegation -If you want to delegate your voting power to multiple wallet addresses, you can do this using the “multidelegation strategy”. This strategy is based on [delegation strategy](https://github.com/snapshot-labs/snapshot-strategies/tree/master/src/strategies/delegation) with the exeption that you can delegate to several addresses at the same time. +If you want to delegate your voting power to multiple wallet addresses, you can do this using the “multidelegation strategy”. This strategy is based on [delegation strategy](https://github.com/snapshot-labs/snapshot-strategies/tree/master/src/strategies/delegation) with the exception that you can delegate to several addresses at the same time. -If A delegates to B and C, A's score is splitted equaly to B and C. In case A already has a delegation made with the [delegation strategy](https://github.com/snapshot-labs/snapshot-strategies/tree/master/src/strategies/delegation), this “multidelegation strategy” overrides it. +If A delegates to B and C, A's score is splitted equally to B and C. In case A already has a delegation made with the [delegation strategy](https://github.com/snapshot-labs/snapshot-strategies/tree/master/src/strategies/delegation), this “multidelegation strategy” overrides it. The multidelegation smart contract is in Polygon, so the gas fee to delegate is way lower. diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index d41486300..931f51274 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -74,6 +74,7 @@ export async function strategy( checksummedAddresses.map((delegate) => { return getAddressTotalDelegatedScore( delegate, + mergedDelegations, reversedDelegations, scores ); diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index 27ac3d6a5..f2c6769f7 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -157,22 +157,29 @@ export function getDelegatorScores( export function getDelegateScore( delegations: string[], - scores: Record[] + scores: Record[], + mergedDelegations: Map ) { - return delegations.reduce( - (delegationsAcumScore, delegatorAddress) => - delegationsAcumScore + getDelegatorScores(scores, delegatorAddress), - 0 - ); + return delegations.reduce((delegationsAcumScore, delegatorAddress) => { + const delegatorDelegations = mergedDelegations.get(delegatorAddress); + const delegationsAmount = delegatorDelegations?.length || 1; + return ( + delegationsAcumScore + + getDelegatorScores(scores, delegatorAddress) / delegationsAmount + ); + }, 0); } export function getAddressTotalDelegatedScore( delegate, + mergedDelegations: Map, reversedDelegations: Map, scores ) { const delegations = reversedDelegations.get(delegate); - const delegateScore = delegations ? getDelegateScore(delegations, scores) : 0; + const delegateScore = delegations + ? getDelegateScore(delegations, scores, mergedDelegations) + : 0; return [delegate, delegateScore]; } diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index ab12d8a6b..e24814309 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -163,6 +163,20 @@ function mockGetScoresDirect() { ); } +function mockScore(delegator: string, delegatorScore: number) { + jest + .spyOn(utils, 'getScoresDirect') + .mockResolvedValue([ + { [delegator]: delegatorScore }, + {}, + {}, + {}, + {}, + {}, + {} + ]); +} + function mockGetScoresDirectNoWMANA() { return jest.spyOn(utils, 'getScoresDirect').mockResolvedValue([ {}, @@ -229,8 +243,10 @@ describe('multidelegation', () => { SNAPSHOT ); - expect(result[ADDRESS_L]).toEqual(DELEGATOR_SCORE * 3); - expect(result[ADDRESS_N]).toEqual(DELEGATOR_SCORE); + expect(result[ADDRESS_L]).toEqual( + DELEGATOR_SCORE * 2 + DELEGATOR_SCORE / 2 + ); + expect(result[ADDRESS_N]).toEqual(DELEGATOR_SCORE / 2); expect(result[ADDRESS_A]).toEqual(DELEGATOR_SCORE); expect(result[ADDRESS_Y]).toEqual(DELEGATOR_SCORE); expect(result[ADDRESS_G]).toEqual(DELEGATOR_SCORE); @@ -249,7 +265,9 @@ describe('multidelegation', () => { SNAPSHOT ); - expect(result[ADDRESS_L]).toEqual(DELEGATOR_SCORE * 3); + expect(result[ADDRESS_L]).toEqual( + DELEGATOR_SCORE * 2 + DELEGATOR_SCORE / 2 + ); expect(result[ADDRESS_LOWERCASE]).toBeUndefined(); }); }); @@ -344,6 +362,28 @@ describe('multidelegation', () => { expect(result[ADDRESS_N]).toEqual(0); }); }); + + describe('when there are multidelegations', () => { + const DELEGATES = [ADDRESS_L, ADDRESS_N]; + const DELEGATOR_SCORE = 1000; + beforeEach(() => { + jest.clearAllMocks(); + mockGetMultiDelegations([[ADDRESS_G, DELEGATES]]); + mockScore(ADDRESS_G, DELEGATOR_SCORE); + }); + it('should split the scores equally between the delegators', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + [ADDRESS_L, ADDRESS_N], + OPTIONS, + SNAPSHOT + ); + expect(result[ADDRESS_L]).toEqual(DELEGATOR_SCORE / DELEGATES.length); + expect(result[ADDRESS_N]).toEqual(DELEGATOR_SCORE / DELEGATES.length); + }); + }); }); describe('getDelegationAddresses', () => { From a8ff2039dad8764419158cc8e7b96a77ef76944c Mon Sep 17 00:00:00 2001 From: ncomerci Date: Wed, 31 May 2023 12:31:09 -0300 Subject: [PATCH 19/23] added test cases & addresses refactor --- test/multidelegation.test.ts | 99 +++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 36 deletions(-) diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index e24814309..d06671667 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -180,14 +180,14 @@ function mockScore(delegator: string, delegatorScore: number) { function mockGetScoresDirectNoWMANA() { return jest.spyOn(utils, 'getScoresDirect').mockResolvedValue([ {}, - { '0x4757cE43Dc5429B8F1A132DC29eF970E55Ae722B': 2000 }, + { [ADDRESS_G]: 2000 }, {}, { - '0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7': 100, - '0x549a9021661a85b6bc51c07b3a451135848d0048': 0, - '0xd7539FCdC0aB79a7B688b04387cb128E75cb77Dc': 127.5, - '0x4757cE43Dc5429B8F1A132DC29eF970E55Ae722B': 120, - '0xC9dA7343583fA8Bb380A6F04A208C612F86C7701': 50 + [ADDRESS_N]: 100, + [ADDRESS_L]: 0, + [ADDRESS_X]: 127.5, + [ADDRESS_G]: 120, + [ADDRESS_A]: 50 }, {}, {}, @@ -384,44 +384,71 @@ describe('multidelegation', () => { expect(result[ADDRESS_N]).toEqual(DELEGATOR_SCORE / DELEGATES.length); }); }); + + describe('when the delegator score is 0', () => { + const DELEGATES = [ADDRESS_L, ADDRESS_N]; + const DELEGATOR_SCORE = 0; + beforeEach(() => { + jest.clearAllMocks(); + mockGetMultiDelegations([[ADDRESS_G, DELEGATES]]); + mockScore(ADDRESS_G, DELEGATOR_SCORE); + }); + it('should be 0 for all delegates', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + [ADDRESS_L, ADDRESS_N], + OPTIONS, + SNAPSHOT + ); + expect(result[ADDRESS_L]).toEqual(0); + expect(result[ADDRESS_N]).toEqual(0); + }); + }); + + describe('when there are empty delegations in polygon', () => { + const DELEGATES = []; + const DELEGATOR_SCORE = 1000; + beforeEach(() => { + jest.clearAllMocks(); + mockGetLegacyDelegations([[ADDRESS_G, ADDRESS_L]]); + mockGetMultiDelegations([[ADDRESS_G, DELEGATES]]); + mockScore(ADDRESS_G, DELEGATOR_SCORE); + }); + it('uses the legacy delegation as fallback', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + [ADDRESS_L, ADDRESS_N, ADDRESS_G], + OPTIONS, + SNAPSHOT + ); + expect(result[ADDRESS_L]).toEqual(DELEGATOR_SCORE); + expect(result[ADDRESS_N]).toEqual(0); + expect(result[ADDRESS_G]).toEqual(0); + }); + }); }); describe('getDelegationAddresses', () => { describe('when it receives a list of addresses with repeated delegations', () => { const reversedDelegations = new Map([ - [ - '0x49E4DbfF86a2E5DA27c540c9A9E8D2C3726E278F', - [ - '0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7', - '0xd90c6f6D37716b1Cc4dd2B116be42e8683550F45', - '0x2AC89522CB415AC333E64F52a1a5693218cEBD58' - ] - ], - [ - '0xC9dA7343583fA8Bb380A6F04A208C612F86C7701', - ['0x49E4DbfF86a2E5DA27c540c9A9E8D2C3726E278F'] - ], - [ - '0x6E33e22f7aC5A4b58A93C7f6D8Da8b46c50A3E20', - ['0xd7539FCdC0aB79a7B688b04387cb128E75cb77Dc'] - ], - [ - '0x4757cE43Dc5429B8F1A132DC29eF970E55Ae722B', - ['0xC9dA7343583fA8Bb380A6F04A208C612F86C7701'] - ], - [ - '0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7', - ['0xd90c6f6D37716b1Cc4dd2B116be42e8683550F45'] - ] + [ADDRESS_L, [ADDRESS_N, ADDRESS_Z, ADDRESS_GS]], + [ADDRESS_A, [ADDRESS_L]], + [ADDRESS_Y, [ADDRESS_X]], + [ADDRESS_G, [ADDRESS_A]], + [ADDRESS_N, [ADDRESS_Z]] ]); it('does not include repeated addresses', () => { expect(getDelegationAddresses(reversedDelegations)).toEqual([ - '0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7', - '0xd90c6f6D37716b1Cc4dd2B116be42e8683550F45', - '0x2AC89522CB415AC333E64F52a1a5693218cEBD58', - '0x49E4DbfF86a2E5DA27c540c9A9E8D2C3726E278F', - '0xd7539FCdC0aB79a7B688b04387cb128E75cb77Dc', - '0xC9dA7343583fA8Bb380A6F04A208C612F86C7701' + ADDRESS_N, + ADDRESS_Z, + ADDRESS_GS, + ADDRESS_L, + ADDRESS_X, + ADDRESS_A ]); }); }); From 62c3d9269f9f70dd9997ab4821843e052e8df15c Mon Sep 17 00:00:00 2001 From: lemu Date: Wed, 31 May 2023 16:02:30 -0300 Subject: [PATCH 20/23] chore: extract multi delegation env vars --- src/strategies/multidelegation/examples.json | 4 ++- src/strategies/multidelegation/index.ts | 16 ++++++++++- src/strategies/multidelegation/utils.ts | 30 ++++++++++++++------ test/multidelegation.test.ts | 18 ++++++++++++ 4 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/strategies/multidelegation/examples.json b/src/strategies/multidelegation/examples.json index 16258bb2b..b3fbafc28 100644 --- a/src/strategies/multidelegation/examples.json +++ b/src/strategies/multidelegation/examples.json @@ -5,6 +5,8 @@ "name": "multidelegation", "params": { "symbol": "VP (delegated)", + "polygonChain": "mumbai", + "delegationSpace": "1emu.eth", "strategies": [ { "name": "erc20-balance-of", @@ -114,6 +116,6 @@ "0xC9dA7343583fA8Bb380A6F04A208C612F86C7701", "0x69ABF813a683391C0ec888351912E14590B56e88" ], - "snapshot": 17331246 + "snapshot": 17380758 } ] diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index 931f51274..ad3b17a25 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -13,6 +13,15 @@ export const author = 'ncomerci'; export const version = '0.1.0'; export const dependOnOtherAddress = true; +const MULTI_DELEGATION_ENV = { + mainnet: { polygonChainId: '137', subgraphUrl: '' }, + mumbai: { + polygonChainId: '80001', + subgraphUrl: + 'https://api.thegraph.com/subgraphs/name/1emu/multi-delegation-polygon' + } +}; + export async function strategy( space, network, @@ -24,6 +33,10 @@ export async function strategy( const delegationSpace = options.delegationSpace || space; const checksummedAddresses = addresses.map(getAddress); + const multiDelegationEnv = + (options?.polygonChain && MULTI_DELEGATION_ENV[options.polygonChain]) || + MULTI_DELEGATION_ENV.mumbai; + // Retro compatibility with the legacy delegation strategy const legacyDelegationsPromise = getLegacyDelegations( delegationSpace, @@ -32,6 +45,7 @@ export async function strategy( snapshot ); const multiDelegationsPromise = getPolygonMultiDelegations( + multiDelegationEnv, network, snapshot, provider, @@ -62,7 +76,7 @@ export async function strategy( const scores = ( await getScoresDirect( space, - options.strategies, + options.strategies || [], network, provider, delegationAddresses, diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index f2c6769f7..486ae844b 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -6,6 +6,7 @@ import { } from '../../utils'; export async function getPolygonDelegatesBySpace( + subgraphUrl: string, space: string, snapshot = 'latest' ) { @@ -36,10 +37,7 @@ export async function getPolygonDelegatesBySpace( while (true) { params.delegations.__args.skip = page * PAGE_SIZE; - const pageResult = await subgraphRequest( - 'https://api.thegraph.com/subgraphs/name/1emu/multi-delegation-polygon', - params - ); + const pageResult = await subgraphRequest(subgraphUrl, params); const pageDelegations = pageResult.delegations || []; result = result.concat(pageDelegations); page++; @@ -50,11 +48,15 @@ export async function getPolygonDelegatesBySpace( } export async function getMultiDelegations( + subgraphUrl: string, space: string, - network: string, snapshot?: string ): Promise> { - const delegatesBySpace = await getPolygonDelegatesBySpace(space, snapshot); + const delegatesBySpace = await getPolygonDelegatesBySpace( + subgraphUrl, + space, + snapshot + ); return delegatesBySpace.reduce((accum, delegation) => { const delegator = getAddress(delegation.delegator); @@ -183,8 +185,12 @@ export function getAddressTotalDelegatedScore( return [delegate, delegateScore]; } -export async function getPolygonBlockNumber(network, snapshot, provider) { - const polygonChainId = '80001'; +export async function getPolygonBlockNumber( + polygonChainId, + network, + snapshot, + provider +) { const blocks = await getSnapshots(network, snapshot, provider, [ polygonChainId ]); @@ -192,18 +198,24 @@ export async function getPolygonBlockNumber(network, snapshot, provider) { } export async function getPolygonMultiDelegations( + multiDelegationEnv, network, snapshot, provider, delegationSpace ) { const polygonBlockNumber = await getPolygonBlockNumber( + multiDelegationEnv.polygonChainId, network, snapshot, provider ); - return getMultiDelegations(delegationSpace, network, polygonBlockNumber); + return getMultiDelegations( + multiDelegationEnv.subgraphUrl, + delegationSpace, + polygonBlockNumber + ); } export function getDelegationAddresses( diff --git a/test/multidelegation.test.ts b/test/multidelegation.test.ts index d06671667..8107baf53 100644 --- a/test/multidelegation.test.ts +++ b/test/multidelegation.test.ts @@ -10,6 +10,7 @@ const NETWORK = '1'; const PROVIDER = snapshot.utils.getProvider(NETWORK); const SPACE = 'some.space.eth'; const OPTIONS = { + polygonChain: 'mumbai', strategies: [ { name: 'erc20-balance-of', @@ -430,6 +431,23 @@ describe('multidelegation', () => { expect(result[ADDRESS_G]).toEqual(0); }); }); + + describe('when the delegation strategy is called without strategies', () => { + const EMPTY_OPTIONS = {}; + it('returns a score for each received address', async () => { + const result = await strategy( + SPACE, + NETWORK, + PROVIDER, + ADDRESSES, + EMPTY_OPTIONS, + SNAPSHOT + ); + + expect(Object.keys(result).length).toEqual(ADDRESSES.length); + expect(result[ADDRESSES[0]]).toEqual(0); + }); + }); }); describe('getDelegationAddresses', () => { From 1c3150ae1eebd80fcd0bfd6ac9b7da9fbd2126a4 Mon Sep 17 00:00:00 2001 From: lemu Date: Wed, 31 May 2023 16:27:31 -0300 Subject: [PATCH 21/23] chore: update README.md --- src/strategies/multidelegation/README.md | 73 +++++++++++++++--------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/strategies/multidelegation/README.md b/src/strategies/multidelegation/README.md index 952d34191..59f0c85ae 100644 --- a/src/strategies/multidelegation/README.md +++ b/src/strategies/multidelegation/README.md @@ -1,39 +1,58 @@ # multidelegation -If you want to delegate your voting power to multiple wallet addresses, you can do this using the “multidelegation strategy”. This strategy is based on [delegation strategy](https://github.com/snapshot-labs/snapshot-strategies/tree/master/src/strategies/delegation) with the exception that you can delegate to several addresses at the same time. +If you want to delegate your voting power to multiple wallet addresses, you can do this using the multidelegation strategy. This strategy is based on [delegation strategy](https://github.com/snapshot-labs/snapshot-strategies/tree/master/src/strategies/delegation) with the exception that you can delegate to several addresses at the same time. -If A delegates to B and C, A's score is splitted equally to B and C. In case A already has a delegation made with the [delegation strategy](https://github.com/snapshot-labs/snapshot-strategies/tree/master/src/strategies/delegation), this “multidelegation strategy” overrides it. +If A delegates to B and C, A's score is split equally to B and C. +In case A already has previously delegated with the [delegation strategy](https://github.com/snapshot-labs/snapshot-strategies/tree/master/src/strategies/delegation), this multidelegation strategy will override it. -The multidelegation smart contract is in Polygon, so the gas fee to delegate is way lower. +The multidelegation smart contract is in Polygon, so the gas fee to delegate is way lower 💸. -| Param Name | Description | -| ----------- | ----------- | -| strategies | list of sub strategies to calculate voting power based on delegation | -| delegationSpace (optional) | Get delegations of a particular space (by default it take delegations of current space) | +| Param Name | Description | +| -------------------------- |-----------------------------------------------------------------------------------------------------------------------------------------------------| +| strategies | List of sub strategies to calculate voting power based on delegation | +| delegationSpace (optional) | Get delegations of a particular space (by default it takes delegations of current space) | +| polygonChain (optional) | Indicates the polygon subgraph to be used to fetch delegations. Possible values are `mumbai` and `mainnet` (by default it uses `mumbai`'s subgraph) | Here is an example of parameters: ```json { - "symbol": "YFI (delegated)", - "strategies": [ - { - "name": "erc20-balance-of", - "params": { - "address": "0xBa37B002AbaFDd8E89a1995dA52740bbC013D992", - "symbol": "YFI", - "decimals": 18 - } - }, - { - "name": "yearn-vault", - "params": { - "address": "0xBA2E7Fed597fd0E3e70f5130BcDbbFE06bB94fe1", - "symbol": "YFI (yYFI)", - "decimals": 18 - } + "name": "Example query", + "strategy": { + "name": "multidelegation", + "params": { + "symbol": "VP (delegated)", + "polygonChain": "mumbai", + "delegationSpace": "1emu.eth", + "strategies": [ + { + "name": "erc20-balance-of", + "params": { + "symbol": "WMANA", + "address": "0xfd09cf7cfffa9932e33668311c4777cb9db3c9be", + "decimals": 18 + } + }, + { + "name": "erc721-with-multiplier", + "params": { + "symbol": "LAND", + "address": "0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d", + "multiplier": 2000 + } + } + ] } - ] + }, + "network": "1", + "addresses": [ + "0x56d0B5eD3D525332F00C9BC938f93598ab16AAA7", + "0x49E4DbfF86a2E5DA27c540c9A9E8D2C3726E278F", + "0xd7539FCdC0aB79a7B688b04387cb128E75cb77Dc", + "0x4757cE43Dc5429B8F1A132DC29eF970E55Ae722B", + "0xC9dA7343583fA8Bb380A6F04A208C612F86C7701", + "0x69ABF813a683391C0ec888351912E14590B56e88" + ], + "snapshot": 17380758 } - -``` \ No newline at end of file +``` From b7a424c610d9ede71a13704f82244139d8b10616 Mon Sep 17 00:00:00 2001 From: lemu Date: Wed, 31 May 2023 16:46:53 -0300 Subject: [PATCH 22/23] chore: move tests --- .../multidelegation}/mergeDelegations.test.ts | 2 +- .../multidelegation}/multidelegation.test.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename test/{ => strategies/multidelegation}/mergeDelegations.test.ts (98%) rename test/{ => strategies/multidelegation}/multidelegation.test.ts (97%) diff --git a/test/mergeDelegations.test.ts b/test/strategies/multidelegation/mergeDelegations.test.ts similarity index 98% rename from test/mergeDelegations.test.ts rename to test/strategies/multidelegation/mergeDelegations.test.ts index 4b3c64f50..6f83fb217 100644 --- a/test/mergeDelegations.test.ts +++ b/test/strategies/multidelegation/mergeDelegations.test.ts @@ -1,4 +1,4 @@ -import { mergeDelegations } from '../src/strategies/multidelegation/utils'; +import { mergeDelegations } from '../../../src/strategies/multidelegation/utils'; describe('when both legacyDelegations and multiDelegations are empty objects', () => { it('returns an empty object', () => { diff --git a/test/multidelegation.test.ts b/test/strategies/multidelegation/multidelegation.test.ts similarity index 97% rename from test/multidelegation.test.ts rename to test/strategies/multidelegation/multidelegation.test.ts index 8107baf53..ffa0897d0 100644 --- a/test/multidelegation.test.ts +++ b/test/strategies/multidelegation/multidelegation.test.ts @@ -1,8 +1,8 @@ -import { strategy } from '../src/strategies/multidelegation'; -import * as utils from '../src/utils'; -import * as multiDelegationUtils from '../src/strategies/multidelegation/utils'; -import { getDelegationAddresses } from '../src/strategies/multidelegation/utils'; -import snapshot from '../src'; +import { strategy } from '../../../src/strategies/multidelegation'; +import * as utils from '../../../src/utils'; +import * as multiDelegationUtils from '../../../src/strategies/multidelegation/utils'; +import { getDelegationAddresses } from '../../../src/strategies/multidelegation/utils'; +import snapshot from '../../../src'; import { getAddress } from '@ethersproject/address'; const SNAPSHOT = 'latest'; From d1692694e60ba279ad33fcbcadea6a65eb239600 Mon Sep 17 00:00:00 2001 From: lemu Date: Wed, 31 May 2023 16:53:45 -0300 Subject: [PATCH 23/23] chore: renames --- src/strategies/multidelegation/index.ts | 16 ++++++++-------- src/strategies/multidelegation/utils.ts | 2 +- .../multidelegation/multidelegation.test.ts | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/strategies/multidelegation/index.ts b/src/strategies/multidelegation/index.ts index ad3b17a25..2add734e1 100644 --- a/src/strategies/multidelegation/index.ts +++ b/src/strategies/multidelegation/index.ts @@ -1,8 +1,8 @@ import { getAddressTotalDelegatedScore, getDelegationAddresses, - getLegacyDelegations, getPolygonMultiDelegations, + getSingleDelegations, mergeDelegations, reverseDelegations } from './utils'; @@ -37,8 +37,8 @@ export async function strategy( (options?.polygonChain && MULTI_DELEGATION_ENV[options.polygonChain]) || MULTI_DELEGATION_ENV.mumbai; - // Retro compatibility with the legacy delegation strategy - const legacyDelegationsPromise = getLegacyDelegations( + // Retro compatibility with the one-to-one delegation strategy + const singleDelegationsPromise = getSingleDelegations( delegationSpace, network, checksummedAddresses, @@ -52,22 +52,22 @@ export async function strategy( delegationSpace ); - const [legacyDelegations, multiDelegations] = await Promise.all([ - legacyDelegationsPromise, + const [singleDelegations, multiDelegations] = await Promise.all([ + singleDelegationsPromise, multiDelegationsPromise ]); - const isLegacyDelegationEmpty = legacyDelegations.size === 0; + const isSingleDelegationEmpty = singleDelegations.size === 0; const isMultiDelegationEmpty = multiDelegations.size === 0; - if (isLegacyDelegationEmpty && isMultiDelegationEmpty) { + if (isSingleDelegationEmpty && isMultiDelegationEmpty) { return Object.fromEntries( checksummedAddresses.map((address) => [address, 0]) ); } const mergedDelegations = mergeDelegations( - legacyDelegations, + singleDelegations, multiDelegations ); const reversedDelegations = reverseDelegations(mergedDelegations); diff --git a/src/strategies/multidelegation/utils.ts b/src/strategies/multidelegation/utils.ts index 486ae844b..c6dfd81f2 100644 --- a/src/strategies/multidelegation/utils.ts +++ b/src/strategies/multidelegation/utils.ts @@ -67,7 +67,7 @@ export async function getMultiDelegations( }, new Map()); } -export async function getLegacyDelegations( +export async function getSingleDelegations( space: string, network: string, addresses: string[], diff --git a/test/strategies/multidelegation/multidelegation.test.ts b/test/strategies/multidelegation/multidelegation.test.ts index ffa0897d0..eb0161bed 100644 --- a/test/strategies/multidelegation/multidelegation.test.ts +++ b/test/strategies/multidelegation/multidelegation.test.ts @@ -129,7 +129,7 @@ const ADDRESS_Z = '0xd90c6f6D37716b1Cc4dd2B116be42e8683550F45'; function mockGetLegacyDelegations(result: string[][]) { return jest - .spyOn(multiDelegationUtils, 'getLegacyDelegations') + .spyOn(multiDelegationUtils, 'getSingleDelegations') .mockResolvedValue( new Map(result.map((array) => [getAddress(array[0]), array[1]])) );