Skip to content
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

Modify PRE operator schema's entity #11

Merged
merged 3 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type StakeData @entity {
type EpochStake @entity {
# ID is the staking prov. add. + epoch counter
id: ID!
stakingProvider: Bytes! # address
stakingProvider: Bytes!
owner: Bytes!
amount: BigInt!
}
Expand Down Expand Up @@ -78,10 +78,13 @@ type TokenholderDelegation implements Delegation @entity {
delegators: [Account!] @derivedFrom(field: "delegatee")
}

type ConfirmedOperator @entity {
type SimplePREApplication @entity {
# ID is the staking provider address
id: ID!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ID should be the operator address, not the staking provider because we are in the PREOperator entity and we should add a relationship between PREOperator and StakeData entity.

Copy link
Member Author

@manumonti manumonti Jun 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I agree with ID being the operator's address.

But I don't see clearly how to add a relationship between PREOperator and StakeData. Do you mean a Reverse Lookup? This will imply to add a new field in StakeData (probably called preoperator), and in my opinion, we shouldn't mix Threshold Network Staking (StakeData) with an Threshold application of the network (PRE).

Or maybe you mean to use One-To-Many relationship? In that case, do you agree with this schema?

type PREOperator @entity {
  # ID is the operator address
  id: ID!
  confirmationTimestamp: BigInt
  stakes: [StakeData!]!
}

The PREOperator entity will include an array of all stakeDatas that use this operator.

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep makes sense. But it should probably be a one-to-one relationship because of this requirement https://github.com/nucypher/nucypher-contracts/blob/main/contracts/contracts/SimplePREApplication.sol#L193 cc @cygnusv.

Another solution could be a schema like this:

type StakeData @entity {
  ...
  applications: [Application!] @derivedFrom(field: "stake")
}

interface Application {
  id: ID!
  stake: StakeData!
  authorizedStakeAmount: BigInt!
}

type PREApplication implements Application @entity {
  id: ID! // id = "operatorAddress-preapplication"
  stake: StakeData!
  authorizedStakeAmount: BigInt!
  confirmationTimestamp: BigInt!
 ... // other fields related to the pre app
}

// other applications in the future eg. RandomBeaconApplication
type RandomBeaconApplication implement Application @entity {
...
}

So in that case we are ready for multi-staking app authorization and the applications field makes sense in the StakeData entity because we authorize applications to use a given stake. What do you think? @manumonti @cygnusv ofc we can address it in a separate PR just a thought.

Copy link
Member Author

@manumonti manumonti Jun 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find your proposal very reasonable and I mostly agree with that.

Only one comment about this:

type PREApplication implements Application @entity {
  id: ID! // id = "operatorAddress-preapplication"
  [...]

I think it could be easier to implement this if id is stakingProviderAdress-preapplication. I understand that this entity is PREApplication so ID should include operator address, but there is a case which makes hard to implement this:

Check this line: https://github.com/nucypher/nucypher-contracts/blob/710b68713b5b8fece0d0b1c5de376dba1d7d68e7/contracts/contracts/SimplePREApplication.sol#L206

If I am not wrong, this means that when an operator is unbonded, an operatorBonded event is emitted with these parameters:

  • stakingProvider = staking provider address
  • operator = 0x00000000000000000

(please @cygnusv , @vzotova can you confirm?)

So, if the ID is composed of the operator address, we can't easily find the PREOperator entity in order to modify or delete it when this type of event is received.

What do you think, @r-czajkowski ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's right and it works for me. I'm curious about David's opinion.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unbonding operator will lead to operator = 0x00000000000000000 in an event

Copy link
Contributor

@r-czajkowski r-czajkowski Jun 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if the ID is composed of the operator address, we can't easily find the PREOperator entity in order to modify or delete it when this type of event is received.

On the other hand, this is probably a rare scenario (but I'm not sure) and there will be 3 apps (at least for now: tbtc, random beacon, and pre) so I don't mind iterating through applications from the StakeData entity and finding the app which contains -preapplication in the ID and then removing it from the store. Just a thought. Both solutions work for me 👍.

Copy link
Member Author

@manumonti manumonti Jun 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @r-czajkowski. Although I think your proposal scheme fits for Threshold Applications (PREApplication, RandomBeacon, TBTC...), IMHO it can't be applied to SimplePREApplication because:

It doesn't make sense to have an authorizedStakeAmount field in SimplePREApplication since events for authorized stake are not emitted yet.

There is a discussion in progress about these events: threshold-network/solidity-contracts#88 (comment).

I have thought about calling the authorizedStake function from SimplePREApplication contract (https://github.com/nucypher/nucypher-contracts/blob/710b68713b5b8fece0d0b1c5de376dba1d7d68e7/contracts/contracts/SimplePREApplication.sol#L95-L101), but the state of this field won't be synced since no new events are emitted if stake amounts are modified.

In any case, about this:

On the other hand, this is probably a rare scenario (but I'm not sure) and there will be 3 apps (at least for now: tbtc, random beacon, and pre) so I don't mind iterating through applications from the StakeData entity and finding the app which contains -preapplication in the ID and then removing it from the store.

As I understand, this can't work in your proposal schema since the applications field is a derived field. And it is not possible to fetch derived fields in the mappings.ts, since these are built at query time. See https://github.com/graphprotocol/graph-ts/issues/219.

In my opinion, we should create a new issue or PR with your proposed schema and address it when a definitive version of some Threshold application (PREApplication, TBTC, RandomBeacon) is deployed.

Please, let me know your opinion about this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I finally created a issue addressing this. So if @r-czajkowski agree, we can continue the conversation there.

#23

stakingProvider: Bytes! # address
operator: Bytes! # address
operator: Bytes!
stake: StakeData!
bondedTimestamp: BigInt!
confirmedTimestamp: BigInt
}

type DAOMetric @entity {
Expand Down
53 changes: 37 additions & 16 deletions src/mapping.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import {
store,
crypto,
ByteArray,
BigInt,
log,
Address,
} from "@graphprotocol/graph-ts"
import { OperatorConfirmed } from "../generated/SimplePREApplication/SimplePREApplication"
import {
OperatorBonded,
OperatorConfirmed,
} from "../generated/SimplePREApplication/SimplePREApplication"
import {
Staked,
ToppedUp,
Expand All @@ -19,7 +23,6 @@ import {
StakeData,
Epoch,
EpochStake,
ConfirmedOperator,
Account,
MinStakeAmount,
} from "../generated/schema"
Expand All @@ -32,6 +35,7 @@ import {
increaseEpochCount,
getOrCreateStakeDelegation,
getOrCreateTokenholderDelegation,
getOrCreatePreApplication,
} from "./utils"

export function handleStaked(event: Staked): void {
Expand Down Expand Up @@ -64,8 +68,10 @@ export function handleStaked(event: Staked): void {
stakeData.keepInTStake = type === "KEEP" ? amount : BigInt.zero()
stakeData.nuInTStake = type === "NU" ? amount : BigInt.zero()
stakeData.totalStaked = stakeData.tStake
.plus(stakeData.keepInTStake)
.plus(stakeData.nuInTStake)
stakeData.nuInTStake =
type === "NU"
? amount
: BigInt.zero().plus(stakeData.keepInTStake).plus(stakeData.nuInTStake)
stakeData.save()

const lastEpoch = getOrCreateLastEpoch()
Expand All @@ -74,7 +80,7 @@ export function handleStaked(event: Staked): void {
lastEpoch.save()

const epochStakes = populateNewEpochStakes(lastEpoch.stakes)
const epochStakeId = getEpochStakeId(stakingProvider.toHexString())
const epochStakeId = getEpochStakeId(stakingProvider)
const epochStake = new EpochStake(epochStakeId)
epochStake.stakingProvider = stakingProvider
epochStake.owner = owner
Expand Down Expand Up @@ -145,7 +151,7 @@ export function handleToppedUp(event: ToppedUp): void {
lastEpoch.save()

const epochStakes = populateNewEpochStakes(lastEpoch.stakes)
const epochStakeId = getEpochStakeId(stakingProvider.toHexString())
const epochStakeId = getEpochStakeId(stakingProvider)
let epochStake = EpochStake.load(epochStakeId)
if (!epochStake) {
epochStake = new EpochStake(epochStakeId)
Expand Down Expand Up @@ -230,7 +236,7 @@ export function handleUnstaked(event: Unstaked): void {
lastEpoch.save()

const epochStakes = populateNewEpochStakes(lastEpoch.stakes)
const epochStakeId = getEpochStakeId(stakingProvider.toHexString())
const epochStakeId = getEpochStakeId(stakingProvider)
const epochStake = EpochStake.load(epochStakeId)
epochStake!.amount = epochStake!.amount.minus(amount)
epochStake!.save()
Expand Down Expand Up @@ -280,16 +286,31 @@ export function handleDelegateVotesChanged(event: DelegateVotesChanged): void {
daoMetric.save()
}

export function handleOperatorConfirmed(event: OperatorConfirmed): void {
let operator = ConfirmedOperator.load(
event.params.stakingProvider.toHexString()
)
if (!operator) {
operator = new ConfirmedOperator(event.params.stakingProvider.toHexString())
export function handleOperatorBonded(event: OperatorBonded): void {
const stakingProvider = event.params.stakingProvider
const operator = event.params.operator
const timestamp = event.params.startTimestamp

const preApplication = getOrCreatePreApplication(stakingProvider)
if (operator === Address.zero()) {
store.remove("SimplePREApplication", stakingProvider.toHexString())
} else {
preApplication.operator = operator
preApplication.stake = stakingProvider.toHexString()
preApplication.bondedTimestamp = timestamp.plus(BigInt.fromString("12"))
preApplication.save()
}
operator.stakingProvider = event.params.stakingProvider
operator.operator = event.params.operator
operator.save()
}

export function handleOperatorConfirmed(event: OperatorConfirmed): void {
const stakingProvider = event.params.stakingProvider
const operator = event.params.operator
const timestamp = event.block.timestamp

const preApplication = getOrCreatePreApplication(stakingProvider)
preApplication.operator = operator
preApplication.confirmedTimestamp = timestamp
preApplication.save()
}

export function handleMinStakeAmountChanged(
Expand Down
14 changes: 14 additions & 0 deletions src/utils/applications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Address } from "@graphprotocol/graph-ts"
import { SimplePREApplication } from "../../generated/schema"

export function getOrCreatePreApplication(
stakingProvider: Address
): SimplePREApplication {
const preApplication = SimplePREApplication.load(
stakingProvider.toHexString()
)

return !preApplication
? new SimplePREApplication(stakingProvider.toHexString())
: preApplication
}
14 changes: 9 additions & 5 deletions src/utils/epochs.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { BigInt } from "@graphprotocol/graph-ts"
import { Address, BigInt } from "@graphprotocol/graph-ts"
import { Epoch, EpochStake, EpochCounter } from "../../generated/schema"

const STAKING_CONTRACT_DEPLOY_TIMESTAMP = 1643633638
const EPOCH_COUNTER_ID = "epoch-counter"

export function getEpochStakeId(stakingProvider: string): string {
const epochCount = getEpochCount()
return `${stakingProvider}-${epochCount}`
export function getEpochStakeId(
stakingProvider: Address,
epoch: i32 = -1
): string {
const epochCount = epoch == -1 ? getEpochCount() : epoch
return `${stakingProvider.toHexString()}-${epochCount}`
}

export function getOrCreateLastEpoch(): Epoch {
Expand All @@ -27,7 +30,8 @@ export function getOrCreateLastEpoch(): Epoch {
export function populateNewEpochStakes(stakes: string[]): string[] {
for (let i = 0; i < stakes.length; i++) {
const epochStake = EpochStake.load(stakes[i])
epochStake!.id = getEpochStakeId(epochStake!.stakingProvider.toHexString())
const stakingProvider = Address.fromBytes(epochStake!.stakingProvider)
epochStake!.id = getEpochStakeId(stakingProvider)
epochStake!.save()
stakes[i] = epochStake!.id
}
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./delegations"
export * from "./epochs"
export * from "./applications"
4 changes: 3 additions & 1 deletion subgraph.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@ dataSources:
apiVersion: 0.0.5
language: wasm/assemblyscript
entities:
- Operator
- SimplePREApplication
abis:
- name: SimplePREApplication
file: ./abis/SimplePREApplication.json
eventHandlers:
- event: OperatorConfirmed(indexed address,indexed address)
handler: handleOperatorConfirmed
- event: OperatorBonded(indexed address,indexed address,uint256)
handler: handleOperatorBonded
file: ./src/mapping.ts