Skip to content

Commit

Permalink
New custom strategy [gatenet-total-staked] (snapshot-labs#578)
Browse files Browse the repository at this point in the history
* New Strategy - Gatenet-Total-Staked

* GraphQL Sungraph Interactions

* Update strategy code

* Update strategy logic and examples

* Update readme.MD for GATENet strategy

* Update README.md

* Convert JSON ABI to String ABI and remove unwanted files.

* Set subgraph URL into parameters

* Temp: Subgraph timestamp condition.

* Update GQL code to limit result to block number & timespamp only

* Remove unwanted code line

* Remove unwanted code line

Co-authored-by: Daz-Mac <[email protected]>
  • Loading branch information
usagar80 and Daz-Mac authored May 12, 2022
1 parent 76d52e5 commit 8b628db
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 0 deletions.
11 changes: 11 additions & 0 deletions src/strategies/gatenet-total-staked/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# GATENet Total Staked Strategy

A custom GATENet Total Staked Strategy to calculate quorum based on the total staked for a wallet's address, based on the GATENet Staking Platform (https://staking.gatenet.io/).

```json
{
"address": "0x6b175474e89094c44da98b954eedeac495271d0f",
"symbol": "GATE",
"decimals": 18
}
```
26 changes: 26 additions & 0 deletions src/strategies/gatenet-total-staked/examples.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"name": "gatenet-total-staked",
"strategy": {
"name": "gatenet-total-staked",
"params": {
"address": "0xB53E4B0F128B231B8728525C616c26E0E59407A1",
"symbol": "GATE",
"decimals": 18,
"subgraph": "https://api.studio.thegraph.com/query/17252/gatenet-cvm/v0.6.1"
}
},
"network": "4",
"addresses": [
"0x8900cCBdC60fD97E3B7c8529A9987F8c0f8A1125",
"0xB868F9378ddbf023938C00A344e6430eeB3a6042",
"0x970DAECA395D1324C674bFFD35cA7e363153Ed1e",
"0x4946D49A2464263deF3cfa194572600343548c4c",
"0xb7cc944b93eA5FD6bBa02810c855B64a1c29eDf5",
"0xF2c6B24058779f740A84aC4a84FAC827b4affC6A",
"0xC48c8F74B4aeC9cC7669700CBfAF83fa7c2469e3",
"0xf61FD1558A7F540A11B8Da876Fce795dE983B8E1"
],
"snapshot": 10537384
}
]
223 changes: 223 additions & 0 deletions src/strategies/gatenet-total-staked/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import { Multicaller, subgraphRequest } from '../../utils';
import { BigNumber, BigNumberish } from '@ethersproject/bignumber';
import { formatUnits } from '@ethersproject/units';

export const author = 'usagar80';
export const version = '0.0.1';

const UNSTAKE = 'Unstake';
const STAKE = 'Stake';
const FEE_DISTRIBUTION = 'Fee distribution';
const REWARD_ADDED = 'Reward';
const CLAIM = 'Claim';
const OTHERS = 'others';

const abi = ['function LOCK_PERIOD() view returns (uint256)'];

const getTransactionType = (transaction) => {
switch (transaction.__typename) {
case 'Staking':
if (transaction.txName === 'FeeDistribution') {
return FEE_DISTRIBUTION;
} else if (transaction.txName === 'RewardAdded') {
return REWARD_ADDED;
} else if (transaction.txName === 'Claim') {
return CLAIM;
}
return OTHERS;
case 'CompoundDeposit':
return STAKE;
default:
return UNSTAKE;
}
};

//gatenet-total-staked
export async function strategy(
space,
network,
provider,
addresses,
options,
snapshot
): Promise<Record<string, number>> {
const blockTag = typeof snapshot === 'number' ? snapshot : 'latest';
const multi = new Multicaller(network, provider, abi, { blockTag });
multi.call('lockPeriod', options.address, 'LOCK_PERIOD');
const multiResult = await multi.execute();
const lockPeriod = Number(multiResult.lockPeriod);
const result: Record<string, BigNumberish> = {};
const args = {
where: {
sender_in: addresses
}
};

if (snapshot !== 'latest') {
// @ts-ignore
args.where.time_lte = (await provider.getBlock(snapshot)).timestamp;
}

const query = {
compoundDeposits: {
__args: args,
__typename: true,
id: true,
sender: true,
amount: true,
shares: true,
time: true
},
compoundWithdraws: {
__args: args,
__typename: true,
id: true,
sender: true,
amount: true,
shares: true,
time: true
},
stakings: {
__typename: true,
id: true,
txName: true,
amount: true,
user: true,
time: true,
unStakeIndex: true
}
};
const transactionsList = await subgraphRequest(options.subgraph, query);

for (const address of addresses) {
let feePerShare = BigNumber.from(0);
let rewardRate = BigNumber.from(0);
let waitingFees = BigNumber.from(0);
let waitingRewards = BigNumber.from(0);
let currentShares = BigNumber.from(0);
let transactionTypeTemp = '';
const ether = BigNumber.from(10).pow(18);

const compoundDeposits = transactionsList.compoundDeposits.filter(
(s) => s.sender.toLowerCase() === address.toLowerCase()
);
const compoundWithdraws = transactionsList.compoundWithdraws.filter(
(s) => s.sender.toLowerCase() === address.toLowerCase()
);

if (compoundWithdraws.length > 0 && compoundDeposits.length > 0) {
const rawTransactions = compoundDeposits
.concat(compoundWithdraws)
.concat(transactionsList.stakings)
.sort(function (a, b) {
return a.time - b.time;
});
const transactions = rawTransactions
.map((transaction) => {
const type = getTransactionType(transaction);
if (type !== OTHERS) {
let amount;
switch (type) {
case STAKE: {
currentShares = BigNumber.from(transaction.shares).add(
currentShares
);
amount = BigNumber.from(transaction.amount);
break;
}

case FEE_DISTRIBUTION: {
const totalShares = BigNumber.from(transaction.unStakeIndex);
const transactionAmount = BigNumber.from(transaction.amount);
const previousFeePerShare = feePerShare;
/// 0
feePerShare = previousFeePerShare.add(
transactionAmount.mul(ether).div(totalShares)
);
// 0
amount = currentShares.mul(
feePerShare.sub(previousFeePerShare).div(ether)
);
waitingFees = waitingFees.add(amount);
break;
}

case REWARD_ADDED: {
const rewardRateIncrease = BigNumber.from(
transaction.unStakeIndex
);
const previousRewardRate = rewardRate;
rewardRate = rewardRate
.add(rewardRateIncrease)
.add(previousRewardRate);

amount = currentShares.mul(rewardRate.sub(previousRewardRate));
waitingRewards = waitingRewards.add(amount);
break;
}
case UNSTAKE: {
currentShares = currentShares.sub(
BigNumber.from(transaction.shares || 0)
);
amount = BigNumber.from(transaction.amount);
break;
}
case 'Claim': {
if (transaction.user.toUpperCase() === address.toUpperCase()) {
if (transactionTypeTemp !== UNSTAKE) {
amount = BigNumber.from(transaction.amount);
}
}
break;
}
}
transactionTypeTemp = type;
return amount
? {
name: type,
timeStamp: transaction.time,
amount
}
: null;
}
})
.filter((transaction) => transaction)
.reverse();
// Copied Content Start
const firstUnstakeIndex = transactions.findIndex(
(x) => x.name === UNSTAKE
);
let filtered = transactions;
if (firstUnstakeIndex >= 0)
filtered = transactions.slice(0, firstUnstakeIndex);
const stake: BigNumberish = filtered
.filter((t) => t.name === STAKE)
.reduce((acc: BigNumberish, transaction) => {
return BigNumber.from(transaction.amount).add(acc);
}, BigNumber.from(0));
const firstUnstaked = transactions[firstUnstakeIndex];
let lockedTransaction: BigNumberish = transactions
.slice(firstUnstakeIndex)
.filter(
(t) =>
firstUnstaked &&
firstUnstaked.timeStamp - t.timeStamp <= lockPeriod &&
t.name === STAKE
)
.reduce((acc: BigNumberish, t) => {
return BigNumber.from(t.amount).add(acc);
}, BigNumber.from(0));
if (!lockedTransaction) lockedTransaction = 0;

result[address] = BigNumber.from(0).add(stake).add(lockedTransaction);
} else {
result[address] = BigNumber.from(0);
}
}
return Object.fromEntries(
Object.entries(result).map(([address, balance]) => [
address,
parseFloat(formatUnits(balance, options.decimals))
])
);
}
38 changes: 38 additions & 0 deletions src/strategies/gatenet-total-staked/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/Strategy",
"definitions": {
"Strategy": {
"title": "Strategy",
"type": "object",
"properties": {
"symbol": {
"type": "string",
"title": "Symbol",
"examples": ["e.g. UNI"],
"maxLength": 16
},
"address": {
"type": "string",
"title": "Contract address",
"examples": ["e.g. 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984"],
"pattern": "^0x[a-fA-F0-9]{40}$",
"minLength": 42,
"maxLength": 42
},
"decimals": {
"type": "number",
"title": "Decimals",
"examples": ["e.g. 18"]
},
"subgraph": {
"type": "string",
"title": "Subgraph URL",
"pattern": "[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)?"
}
},
"required": ["address", "decimals"],
"additionalProperties": false
}
}
}
2 changes: 2 additions & 0 deletions src/strategies/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ import * as digitalaxLPStakers from './digitalax-lp-stakers';
import * as digitalaxMonaStakersMatic from './digitalax-mona-stakers-matic';
import * as digitalaxLPStakersMatic from './digitalax-lp-stakers-matic';
import * as galaxyNftWithScore from './galaxy-nft-with-score';
import * as gatenetTotalStaked from './gatenet-total-staked';
import * as vesper from './vesper';
import * as thales from './thales';
import * as bscMvb from './bsc-mvb';
Expand Down Expand Up @@ -529,6 +530,7 @@ const strategies = {
'digitalax-lp-stakers-matic': digitalaxLPStakersMatic,
'colony-reputation': colonyReputation,
'galaxy-nft-with-score': galaxyNftWithScore,
'gatenet-total-staked': gatenetTotalStaked,
vesper,
thales,
'tech-quadratic-ranked-choice': techQuadraticRankedChoice,
Expand Down

0 comments on commit 8b628db

Please sign in to comment.