-
Notifications
You must be signed in to change notification settings - Fork 767
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[moxie] Adding Strategy For Moxie Voting Power Calculation #1604
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
02a36f3
Adding Snapshot Strategy for Moxie
Hrishikesh-Thakkar 164f612
Merge branch 'master' into master
Hrishikesh-Thakkar cc9ed51
adding page limit in subgraph calls
Hrishikesh-Thakkar 5429642
Adding URLs through options
Hrishikesh-Thakkar 8e6e988
Merge branch 'master' into master
Hrishikesh-Thakkar b7db999
Update src/strategies/index.ts
Hrishikesh-Thakkar 68f8e80
fixing indentation
Hrishikesh-Thakkar beff45d
updating example
Hrishikesh-Thakkar 90d4f5d
Updating PR Feedback to add env
Hrishikesh-Thakkar 10c4c2f
Merge branch 'master' into master
Hrishikesh-Thakkar eb43e80
Adding error check for addresses not equal to 1
Hrishikesh-Thakkar b7138f5
updating the example
Hrishikesh-Thakkar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
SNAPSHOT_API_STRATEGY_SALT=12345 # Salt for the snapshot API strategy to send a key in header (optional) | ||
PASSPORT_API_KEY= # API key for the passport API (optional for other strategies) | ||
PASSPORT_SCORER_ID= # Scorer ID for the passport API (optional for other strategies) | ||
MOXIE_API_KEY= # API key for the moxie APIs (optional for other strategies) | ||
MOXIE_PROTOCOL_ID= #Protocol ID for the moxie API (optional for other strategies) | ||
MOXIE_VESTING_ID= # Vesting ID for the moxie API (optional for other strategies) | ||
MOXIE_LIQUDITY_ID= # Liquidity ID for the moxie API (optional for other strategies) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# moxie | ||
|
||
This strategy returns the voting power of users. This takes into consideration | ||
1. Vesting Contract Moxie Balance | ||
2. Wallet Moxie Balance | ||
3. Vesting Contract Fan Token Balance | ||
4. Wallet Fan Token Balance | ||
5. Liquidity Pool Contributions | ||
6. Staked fan tokens for Wallet & Vesting Contracts | ||
|
||
There are special multipliers provided for Fan Token Balances and Liquidity Pool Contributions | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[ | ||
{ | ||
"name": "Example query", | ||
"strategy": { | ||
"name": "moxie" | ||
}, | ||
"network": "8453", | ||
"addresses": [ | ||
"0xb59aa5bb9270d44be3fa9b6d67520a2d28cf80ab" | ||
], | ||
"snapshot": 20631331 | ||
} | ||
] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
import { BigNumberish } from '@ethersproject/bignumber'; | ||
import { formatUnits } from '@ethersproject/units'; | ||
import { Multicaller, subgraphRequest } from '../../utils'; | ||
import { getAddress } from '@ethersproject/address'; | ||
|
||
export const author = 'Hrishikesh-Thakkar'; | ||
export const version = '0.0.1'; | ||
|
||
const abi = [ | ||
'function balanceOf(address account) external view returns (uint256)' | ||
]; | ||
|
||
const QUERY_LIMIT = 1000; | ||
const UNSTAKED_FAN_TOKEN_MULTIPLIER = 2; | ||
const STAKED_FAN_TOKEN_MULTIPLIER = 3; | ||
const MOXIE_LIQUIDITY_MULTIPLIER = 2; | ||
const MOXIE_CONTRACT_ADDRESS = "0x8C9037D1Ef5c6D1f6816278C7AAF5491d24CD527"; | ||
const MOXIE_DECIMALS = 18; | ||
const PORTFOLIO_SUBGRAPH_PAGE_LIMIT = 2; | ||
const LIQUIDITY_POOL_SUBGRAPH_PAGE_LIMIT = 1; | ||
|
||
//Strategy to Compute Voting Power for MoxieDAO | ||
export async function strategy( | ||
space, | ||
network, | ||
provider, | ||
addresses, | ||
options, | ||
snapshot | ||
) { | ||
//Check if the addresses array has length not equal to 1 | ||
if(addresses.length != 1) { | ||
throw new Error("This strategy expects a single address"); | ||
}; | ||
const MOXIE_API_KEY = process.env.MOXIE_API_KEY || ""; | ||
const MOXIE_PROTOCOL_ID = process.env.MOXIE_PROTOCOL_ID || ""; | ||
const MOXIE_VESTING_ID = process.env.MOXIE_VESTING_ID || ""; | ||
const MOXIE_LIQUIDITY_ID = process.env.MOXIE_LIQUIDITY_ID || ""; | ||
|
||
//SETTING DEFAULT SUBGRAPH URLS | ||
let MOXIE_PROTOCOL_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-protocol/version/latest"; | ||
let MOXIE_VESTING_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-vesting/version/latest"; | ||
let MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-liquidity/version/latest"; | ||
|
||
if (MOXIE_API_KEY !== "" && MOXIE_PROTOCOL_ID !== "") { | ||
MOXIE_PROTOCOL_SUBGRAPH_URL = `https://gateway.thegraph.com/api/${MOXIE_API_KEY}/subgraphs/id/${MOXIE_PROTOCOL_ID}`; | ||
} | ||
if (MOXIE_API_KEY !== "" && MOXIE_LIQUIDITY_ID !== "") { | ||
MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL = `https://gateway.thegraph.com/api/${MOXIE_API_KEY}/subgraphs/id/${MOXIE_LIQUIDITY_ID}`; | ||
} | ||
if (MOXIE_API_KEY !== "" && MOXIE_VESTING_ID !== "") { | ||
MOXIE_VESTING_SUBGRAPH_URL = `https://gateway.thegraph.com/api/${MOXIE_API_KEY}/subgraphs/id/${MOXIE_VESTING_ID}`; | ||
} | ||
|
||
//Check if the snapshot is for a specific block number or it's latest | ||
const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; | ||
const addressesMap = addresses.reduce((map, address) => { | ||
map[getAddress(address)] = 0; | ||
return map; | ||
}, {}); | ||
|
||
const lowercaseAddresses = Object.keys(addressesMap).map((address) => | ||
address.toLowerCase() | ||
); | ||
|
||
// Once we have the addresses we need to query the subgraphs to get the vesting contract addresses | ||
const vestingSubgraphQuery = { | ||
tokenLockWallets: { | ||
__args: { | ||
where: { | ||
beneficiary_in: lowercaseAddresses | ||
} | ||
}, | ||
id: true, | ||
beneficiary: true, | ||
} | ||
}; | ||
//Adding block to the query if the snapshot is not latest | ||
if (snapshot !== 'latest') { | ||
vestingSubgraphQuery.tokenLockWallets.__args['block'] = { number: snapshot }; | ||
} | ||
|
||
//Query the vesting subgraph to get the vesting contract addresses | ||
const vestingContractResponse = await subgraphRequest(MOXIE_VESTING_SUBGRAPH_URL, vestingSubgraphQuery); | ||
|
||
// Generate a map of vesting contract addresses to beneficiaries | ||
const addressToBeneficiaryMap = vestingContractResponse.tokenLockWallets.reduce((map, wallet) => { | ||
map[wallet.id.toLowerCase()] = wallet.beneficiary.toLowerCase(); | ||
return map; | ||
}, {}); | ||
|
||
// Add vesting contract addresses to the list of addresses to query | ||
const allAddresses = [ | ||
...Object.keys(addressToBeneficiaryMap), | ||
...lowercaseAddresses | ||
]; | ||
|
||
// Initialise all the addresses with a score of 0 | ||
const allAddressesScoreMap = allAddresses.reduce((map, address) => { | ||
map[getAddress(address)] = 0; | ||
return map; | ||
}, {}); | ||
|
||
const protocolSubgraphQuery = { | ||
portfolios: { | ||
__args: { | ||
where: { | ||
user_in: allAddresses, | ||
balance_gt: 0 | ||
}, | ||
first: QUERY_LIMIT, | ||
skip: 0 | ||
}, | ||
unstakedBalance: true, | ||
stakedBalance: true, | ||
subjectToken: { | ||
currentPriceInMoxie: true | ||
}, | ||
user: { | ||
id: true | ||
} | ||
} | ||
}; | ||
|
||
const liquidityPoolSubgraphQuery = { | ||
userPools: { | ||
__args: { | ||
where: { | ||
user_in: allAddresses, | ||
}, | ||
first: QUERY_LIMIT, | ||
skip: 0 | ||
}, | ||
totalLPAmount: true, | ||
pool: { | ||
totalSupply: true, | ||
moxieReserve: true, | ||
}, | ||
user: { | ||
id: true | ||
} | ||
} | ||
}; | ||
|
||
//Adding block to the queries if the snapshot is not latest | ||
if (snapshot !== 'latest') { | ||
protocolSubgraphQuery.portfolios.__args['block'] = { number: snapshot }; | ||
liquidityPoolSubgraphQuery.userPools.__args['block'] = { number: snapshot }; | ||
} | ||
|
||
const fetchSubgraphData = async (subgraphUrl, query, processFunction, key, pageLimit) => { | ||
let next_page = 0; | ||
while (next_page < pageLimit) { | ||
query[key].__args.skip = next_page * QUERY_LIMIT; | ||
const response = await subgraphRequest(subgraphUrl, query); | ||
const data = response[Object.keys(response)[0]]; | ||
|
||
processFunction(data); | ||
|
||
if (data.length < QUERY_LIMIT) break; | ||
next_page++; | ||
} | ||
}; | ||
|
||
const processProtocolData = (portfolios) => { | ||
portfolios.forEach((portfolio) => { | ||
const userAddress = getAddress(portfolio.user.id); | ||
allAddressesScoreMap[userAddress] += parseFloat(formatUnits(portfolio.unstakedBalance, MOXIE_DECIMALS)) * UNSTAKED_FAN_TOKEN_MULTIPLIER * portfolio.subjectToken.currentPriceInMoxie + | ||
parseFloat(formatUnits(portfolio.stakedBalance, MOXIE_DECIMALS)) * STAKED_FAN_TOKEN_MULTIPLIER * portfolio.subjectToken.currentPriceInMoxie; | ||
}); | ||
}; | ||
|
||
const processLiquidityPoolData = (userPools) => { | ||
userPools.forEach((userPool) => { | ||
const userAddress = getAddress(userPool.user.id); | ||
allAddressesScoreMap[userAddress] += MOXIE_LIQUIDITY_MULTIPLIER * parseFloat(formatUnits(userPool.totalLPAmount, MOXIE_DECIMALS)) * | ||
parseFloat(formatUnits(userPool.pool.moxieReserve, MOXIE_DECIMALS)) / | ||
parseFloat(formatUnits(userPool.pool.totalSupply, MOXIE_DECIMALS)); | ||
}); | ||
}; | ||
|
||
// RPC Call to get balance of Moxie at a block for users | ||
const fetchBalances = async () => { | ||
const multi = new Multicaller(network, provider, abi, { blockTag }); | ||
allAddresses.forEach((address) => | ||
multi.call(address, MOXIE_CONTRACT_ADDRESS, 'balanceOf', [address]) | ||
); | ||
const result: Record<string, BigNumberish> = await multi.execute(); | ||
Object.entries(result).forEach(([address, balance]) => { | ||
let formattedBalance = parseFloat(formatUnits(balance, MOXIE_DECIMALS)); | ||
allAddressesScoreMap[getAddress(address)] += formattedBalance; | ||
}); | ||
}; | ||
|
||
// Fetch data from both subgraphs in parallel | ||
await Promise.all([ | ||
fetchSubgraphData(MOXIE_PROTOCOL_SUBGRAPH_URL, protocolSubgraphQuery, processProtocolData, "portfolios", PORTFOLIO_SUBGRAPH_PAGE_LIMIT), | ||
fetchSubgraphData(MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL, liquidityPoolSubgraphQuery, processLiquidityPoolData, "userPools", LIQUIDITY_POOL_SUBGRAPH_PAGE_LIMIT), | ||
fetchBalances(), | ||
]); | ||
|
||
// Now we have the score for each address we need to ensure it is added to the beneficiary address if it exists | ||
Object.keys(allAddressesScoreMap).forEach((address) => { | ||
const beneficiary = addressToBeneficiaryMap[address.toLowerCase()]; | ||
if (beneficiary) { | ||
addressesMap[getAddress(beneficiary)] += allAddressesScoreMap[address]; | ||
} else { | ||
addressesMap[address] += allAddressesScoreMap[address]; | ||
} | ||
}); | ||
|
||
return addressesMap; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throw a error if addresses contain more than 1 address (just not to confuse) we can ignore errors in tests related to this change (need to modify examples.json to pass only one address)