Skip to content

Commit

Permalink
feat: added DataEdge contract with OracleConfiguration (#2)
Browse files Browse the repository at this point in the history
* feat: added DataEdge contract with OracleConfiguration

* fix: keep oracle history by using address as the ID

* chore: changed to use subgraph ids

* chore: use subgraphs deployment id and use version instead of commit hash
  • Loading branch information
Maikol authored Jun 14, 2024
1 parent 813151e commit 63daf0e
Show file tree
Hide file tree
Showing 15 changed files with 430 additions and 533 deletions.
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
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();
}
}
}
Loading

0 comments on commit 63daf0e

Please sign in to comment.