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

feat: added DataEdge contract with OracleConfiguration #2

Merged
merged 4 commits into from
Jun 14, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ config/addresses.ts
config/generatedAddresses.json
tests/.bin
.vscode
.latest.json
.latest.json
.docker
16 changes: 16 additions & 0 deletions abis/SAODataEdge.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "Log",
"type": "event"
},
{ "stateMutability": "payable", "type": "fallback" }
]
4 changes: 4 additions & 0 deletions networks.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"SubgraphAvailabilityManager": {
"address": "0x9bDC3264596850E7F5d141A8D898dFA7001355CC",
"startBlock": 22552633
},
"SAODataEdge": {
"address": "0xB61AF143c79Cbdd68f179B657AaC86665CC2B469",
"startBlock": 25463619
}
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"prep:test": "mustache ./config/test.json subgraph.template.yaml > subgraph.yaml"
},
"dependencies": {
"@graphprotocol/graph-cli": "0.61.0",
"@graphprotocol/graph-cli": "0.71.0",
"@graphprotocol/graph-ts": "0.30.0"
},
"devDependencies": {
Expand Down
67 changes: 32 additions & 35 deletions schema.graphql
Original file line number Diff line number Diff line change
@@ -1,45 +1,42 @@
type NewOwnership @entity(immutable: true) {
id: Bytes!
from: Bytes! # address
to: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
type GlobalState @entity {
id: ID!
oracles: [Oracle!]! @derivedFrom(field: "state")
activeOracles: [Oracle!]!
}

type NewPendingOwnership @entity(immutable: true) {
id: Bytes!
from: Bytes! # address
to: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}

type OracleSet @entity(immutable: true) {
id: Bytes!
index: BigInt! # uint256
oracle: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
type Oracle @entity {
id: Bytes! # address
Maikol marked this conversation as resolved.
Show resolved Hide resolved
index: String! # oracle_index
state: GlobalState!
latestConfig: OracleConfiguration!
configurations: [OracleConfiguration!]! @derivedFrom(field: "oracle")
votes: [OracleVote!]! @derivedFrom(field: "oracle")
active: Boolean!
activeSince: BigInt!
activeUntil: BigInt! # 0 means active
}

type OracleVote @entity(immutable: true) {
id: Bytes!
id: ID!
oracle: Oracle!
subgraphDeploymentID: Bytes! # bytes32
deny: Boolean! # bool
oracleIndex: BigInt! # uint256
timestamp: BigInt! # uint256
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}

type VoteTimeLimitSet @entity(immutable: true) {
id: Bytes!
voteTimeLimit: BigInt! # uint256
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type OracleConfiguration @entity(immutable: true) {
id: ID!
oracle: Oracle!
version: String!
ipfsConcurrency: String!
ipfsTimeout: String!
minSignal: String!
period: String!
gracePeriod: String!
supportedDataSourceKinds: String!
networkSubgraphDeploymentId: String!
epochBlockOracleSubgraphDeploymentId: String!
subgraphAvailabilityManagerContract: String!
oracleIndex: String!
createdAt: BigInt!
}
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export let ORACLE_CONFIGURATION_ABI =
"(string,(string,string,string,string,string,string,string,string,string,string))"
31 changes: 31 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { log, BigInt, Bytes } from "@graphprotocol/graph-ts";
import { StoreCache } from "./store-cache";

export function parseCalldata(calldata: Bytes): Bytes {
// Remove function signature
let dataWithoutSignature = calldata.toHexString().slice(10);

// ethabi expects a tuple offset if your tuple contains dynamic data
// https://medium.com/@r2d2_68242/indexing-transaction-input-data-in-a-subgraph-6ff5c55abf20
let hexStringToDecode = '0x0000000000000000000000000000000000000000000000000000000000000020' +
dataWithoutSignature;
return Bytes.fromHexString(hexStringToDecode);
}

export function isSubmitterAllowed(
cache: StoreCache,
oracleIndex: String,
submitter: Bytes
): boolean {
let oracle = cache.getOracle(submitter);
return oracle.active && oracle.index == oracleIndex;
}


export function getOracleVoteId(
subgraphDeploymentID: String,
oracleAddress: String,
timestamp: String
): string {
return [subgraphDeploymentID, oracleAddress, timestamp].join("-") as string;
}
54 changes: 54 additions & 0 deletions src/sao-data-edge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { log, ethereum, Bytes } from "@graphprotocol/graph-ts";
import { Log as LogEvent } from "../generated/SAODataEdge/SAODataEdge"
import { parseCalldata, isSubmitterAllowed } from "./helpers"
import { StoreCache } from "./store-cache";
import { ORACLE_CONFIGURATION_ABI } from "./constants";

export function handleLog(event: LogEvent): void {
let submitter = event.transaction.from;
processPayload(submitter, event.params.data, event.transaction.hash.toHexString(), event.block);
}

export function processPayload(
submitter: Bytes,
payload: Bytes,
txHash: string,
block: ethereum.Block
): void {
let submitterAddress = submitter.toHexString();
log.warning("Processing payload. Submitter: {}", [submitterAddress]);

let cache = new StoreCache();

let parsedCalldata = parseCalldata(payload);
let decoded = ethereum.decode(ORACLE_CONFIGURATION_ABI, parsedCalldata)!.toTuple();
let decodedConfig = decoded[1].toTuple();
let decodedOracleIndex = decodedConfig[9].toString();

if (!isSubmitterAllowed(cache, decodedOracleIndex, submitter)) {
log.error("Submitter not allowed: {}", [submitterAddress]);
return;
}

log.info("Submitter allowed", []);

let oracle = cache.getOracle(submitter);
let config = cache.getOracleConfiguration(txHash);
config.oracle = oracle.id;
config.version = decoded[0].toString();
config.ipfsConcurrency = decodedConfig[0].toString();
config.ipfsTimeout = decodedConfig[1].toString();
config.minSignal = decodedConfig[2].toString();
config.period = decodedConfig[3].toString();
config.gracePeriod = decodedConfig[4].toString();
config.supportedDataSourceKinds = decodedConfig[5].toString();
config.networkSubgraphDeploymentId = decodedConfig[6].toString();
config.epochBlockOracleSubgraphDeploymentId = decodedConfig[7].toString();
config.subgraphAvailabilityManagerContract = decodedConfig[8].toString();
config.oracleIndex = decodedOracleIndex;
config.createdAt = block.timestamp;

oracle.latestConfig = config.id;

cache.commitChanges();
}
87 changes: 87 additions & 0 deletions src/store-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { GlobalState, Oracle, OracleConfiguration, OracleVote } from "../generated/schema"
import { log, Bytes } from "@graphprotocol/graph-ts"

export class SafeMap<K, V> extends Map<K, V> {
safeGet(id: K): V | null {
return this.has(id) ? this.get(id) : null;
}
}

export class StoreCache {
state: GlobalState;
oracles: SafeMap<Bytes, Oracle>;
oraclesConfigs: SafeMap<String, OracleConfiguration>;
oracleVotes: SafeMap<String, OracleVote>;

constructor() {
let state = GlobalState.load("0");
if (state == null) {
state = new GlobalState("0");
state.activeOracles = [];
state.save();
}

this.state = state;
this.oracles = new SafeMap<Bytes, Oracle>();
this.oraclesConfigs = new SafeMap<String, OracleConfiguration>();
this.oracleVotes = new SafeMap<String, OracleVote>();
}

getGlobalState(): GlobalState {
return this.state;
}

getOracle(id: Bytes): Oracle {
if (this.oracles.safeGet(id) == null) {
let oracle = Oracle.load(id);
if (oracle == null) {
oracle = new Oracle(id);
oracle.state = this.state.id;
oracle.index = "";
}
this.oracles.set(id, oracle);
}
return this.oracles.safeGet(id)!;
}

getOracleConfiguration(id: String): OracleConfiguration {
if (this.oraclesConfigs.safeGet(id) == null) {
let config = OracleConfiguration.load(id);
if (config == null) {
config = new OracleConfiguration(id);
}
this.oraclesConfigs.set(id, config);
}
return this.oraclesConfigs.safeGet(id)!;
}

getOracleVote(id: String): OracleVote {
if (this.oracleVotes.safeGet(id) == null) {
let vote = OracleVote.load(id);
if (vote == null) {
vote = new OracleVote(id);
}
this.oracleVotes.set(id, vote);
}
return this.oracleVotes.safeGet(id)!;
}

commitChanges(): void {
this.state.save();

let oracles = this.oracles.values();
for (let i = 0; i < oracles.length; i++) {
oracles[i].save();
}

let configs = this.oraclesConfigs.values();
for (let i = 0; i < configs.length; i++) {
configs[i].save();
}

let votes = this.oracleVotes.values();
for (let i = 0; i < votes.length; i++) {
votes[i].save();
}
}
}