diff --git a/subgraph/harb/.gitignore b/subgraph/harb/.gitignore new file mode 100644 index 0000000..ca12a07 --- /dev/null +++ b/subgraph/harb/.gitignore @@ -0,0 +1,4 @@ +build/ +node_modules/ +generated/ +yarn.lock \ No newline at end of file diff --git a/subgraph/harb/README.md b/subgraph/harb/README.md new file mode 100644 index 0000000..d7eb4e2 --- /dev/null +++ b/subgraph/harb/README.md @@ -0,0 +1,9 @@ + + +## deployment + +``` +yarn codegen +yarn build +yarn deploy +``` \ No newline at end of file diff --git a/subgraph/harb/abis/Stake.json b/subgraph/harb/abis/Stake.json new file mode 100644 index 0000000..70e7688 --- /dev/null +++ b/subgraph/harb/abis/Stake.json @@ -0,0 +1,209 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_tokenContract", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { "internalType": "address", "name": "receiver", "type": "address" }, + { "internalType": "uint256", "name": "stakeWanted", "type": "uint256" }, + { "internalType": "uint256", "name": "availableStake", "type": "uint256" } + ], + "name": "ExceededAvailableStake", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "requester", "type": "address" }, + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "NoPermission", + "type": "error" + }, + { "inputs": [], "name": "PositionNotFound", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "receiver", "type": "address" }, + { "internalType": "uint256", "name": "assets", "type": "uint256" }, + { "internalType": "uint256", "name": "sharesWanted", "type": "uint256" }, + { "internalType": "uint256", "name": "minStake", "type": "uint256" } + ], + "name": "SharesTooLow", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "receiver", "type": "address" }, + { "internalType": "uint64", "name": "taxRateWanted", "type": "uint64" }, + { "internalType": "uint64", "name": "taxRateMet", "type": "uint64" }, + { "internalType": "uint256", "name": "positionId", "type": "uint256" } + ], + "name": "TaxTooLow", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "positionId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "share", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "creationTime", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "taxRate", + "type": "uint32" + } + ], + "name": "PositionCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "positionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "share", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "lastTaxTime", + "type": "uint32" + } + ], + "name": "PositionRemoved", + "type": "event" + }, + { + "inputs": [], + "name": "dormantSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "positionId", "type": "uint256" } + ], + "name": "exitPosition", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "minStake", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nextPositionId", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "outstandingStake", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "positionID", "type": "uint256" } + ], + "name": "payTax", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "positions", + "outputs": [ + { "internalType": "uint256", "name": "share", "type": "uint256" }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "uint32", "name": "creationTime", "type": "uint32" }, + { "internalType": "uint32", "name": "lastTaxTime", "type": "uint32" }, + { "internalType": "uint32", "name": "taxRate", "type": "uint32" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "assets", "type": "uint256" }, + { "internalType": "address", "name": "receiver", "type": "address" }, + { "internalType": "uint32", "name": "taxRate", "type": "uint32" }, + { + "internalType": "uint256[]", + "name": "positionsToSnatch", + "type": "uint256[]" + } + ], + "name": "snatch", + "outputs": [ + { "internalType": "uint256", "name": "positionId", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "positionID", "type": "uint256" }, + { + "internalType": "uint256", + "name": "taxFloorDuration", + "type": "uint256" + } + ], + "name": "taxDue", + "outputs": [ + { "internalType": "uint256", "name": "amountDue", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/subgraph/harb/networks.json b/subgraph/harb/networks.json index 20f30e2..608207f 100644 --- a/subgraph/harb/networks.json +++ b/subgraph/harb/networks.json @@ -3,6 +3,10 @@ "Harb": { "address": "0xcd02666582a2057085edabc55c5120155ba4e93c", "startBlock": 5510934 + }, + "Stake": { + "address": "0x4256777543814d66f9f66390f6bb33c55a24c331", + "startBlock": 5510934 } } } \ No newline at end of file diff --git a/subgraph/harb/package.json b/subgraph/harb/package.json index 2cccd4c..f9b9edd 100644 --- a/subgraph/harb/package.json +++ b/subgraph/harb/package.json @@ -12,7 +12,8 @@ }, "dependencies": { "@graphprotocol/graph-cli": "0.68.5", - "@graphprotocol/graph-ts": "0.32.0" + "@graphprotocol/graph-ts": "0.32.0", + "assemblyscript": "0.19.23" }, "devDependencies": { "matchstick-as": "0.5.0" } } diff --git a/subgraph/harb/schema.graphql b/subgraph/harb/schema.graphql index a1f402e..4ccb920 100644 --- a/subgraph/harb/schema.graphql +++ b/subgraph/harb/schema.graphql @@ -1,27 +1,23 @@ -type Approval @entity(immutable: true) { + +type Stats @entity { + id: Bytes! + outstandingSupply: BigInt! # uint256 + activeSupply: BigInt! # uint256 +} + +enum PositionStatus { + Active, + Closed, +} + +type Position @entity { id: Bytes! owner: Bytes! # address - spender: Bytes! # address - value: BigInt! # uint256 - blockNumber: BigInt! - blockTimestamp: BigInt! - transactionHash: Bytes! -} - -type EIP712DomainChanged @entity(immutable: true) { - id: Bytes! - - blockNumber: BigInt! - blockTimestamp: BigInt! - transactionHash: Bytes! -} - -type Transfer @entity(immutable: true) { - id: Bytes! - from: Bytes! # address - to: Bytes! # address - value: BigInt! # uint256 - blockNumber: BigInt! - blockTimestamp: BigInt! - transactionHash: Bytes! + share: BigDecimal! + # harb at start + # current harb value + creationTime: Int! + lastTaxTime: Int + taxRate: BigDecimal! + status: PositionStatus! } diff --git a/subgraph/harb/src/harb.ts b/subgraph/harb/src/harb.ts index e107238..351cadc 100644 --- a/subgraph/harb/src/harb.ts +++ b/subgraph/harb/src/harb.ts @@ -3,48 +3,17 @@ import { EIP712DomainChanged as EIP712DomainChangedEvent, Transfer as TransferEvent } from "../generated/Harb/Harb" -import { Approval, EIP712DomainChanged, Transfer } from "../generated/schema" export function handleApproval(event: ApprovalEvent): void { - let entity = new Approval( - event.transaction.hash.concatI32(event.logIndex.toI32()) - ) - entity.owner = event.params.owner - entity.spender = event.params.spender - entity.value = event.params.value - entity.blockNumber = event.block.number - entity.blockTimestamp = event.block.timestamp - entity.transactionHash = event.transaction.hash - - entity.save() } export function handleEIP712DomainChanged( event: EIP712DomainChangedEvent ): void { - let entity = new EIP712DomainChanged( - event.transaction.hash.concatI32(event.logIndex.toI32()) - ) - - entity.blockNumber = event.block.number - entity.blockTimestamp = event.block.timestamp - entity.transactionHash = event.transaction.hash - - entity.save() + } export function handleTransfer(event: TransferEvent): void { - let entity = new Transfer( - event.transaction.hash.concatI32(event.logIndex.toI32()) - ) - entity.from = event.params.from - entity.to = event.params.to - entity.value = event.params.value - - entity.blockNumber = event.block.number - entity.blockTimestamp = event.block.timestamp - entity.transactionHash = event.transaction.hash - - entity.save() + } diff --git a/subgraph/harb/src/stake.ts b/subgraph/harb/src/stake.ts new file mode 100644 index 0000000..44f16e2 --- /dev/null +++ b/subgraph/harb/src/stake.ts @@ -0,0 +1,27 @@ +import { + PositionCreated as PositionCreatedEvent, + PositionRemoved as PositionRemovedEvent, +} from "../generated/Stake/Stake" +import { BigDecimal, BigInt, Bytes } from "@graphprotocol/graph-ts" +import { Position } from "../generated/schema" + +let decimals: BigDecimal = BigDecimal.fromString("1000000000000000000") +let totalSupply: BigDecimal = BigDecimal.fromString("10000000000000000000000000") + +export function handlePositionCreated(event: PositionCreatedEvent): void { + let position = new Position(Bytes.fromI32(event.params.positionId.toI32())) + position.owner = event.params.owner + position.share = event.params.share.toBigDecimal().div(totalSupply) + position.creationTime = event.params.creationTime.toI32(); + position.taxRate = event.params.taxRate.toBigDecimal().div(BigDecimal.fromString("100")) + position.status = "Active" + position.save() +} + +export function handlePositionRemoved(event: PositionRemovedEvent): void { + let position = Position.load(Bytes.fromI32(event.params.positionId.toI32())) + if (position != null) { + position.status = "Closed" + position.save() + } +} diff --git a/subgraph/harb/subgraph.yaml b/subgraph/harb/subgraph.yaml index 5d0b73f..a4a2898 100644 --- a/subgraph/harb/subgraph.yaml +++ b/subgraph/harb/subgraph.yaml @@ -1,6 +1,6 @@ -specVersion: 1.0.0 -indexerHints: - prune: auto +specVersion: 0.0.4 +repository: http://gitea.loseyourip.com:4000/dark-meme-society/harb.git +description: Harberger Tax Token schema: file: ./schema.graphql dataSources: @@ -16,9 +16,7 @@ dataSources: apiVersion: 0.0.7 language: wasm/assemblyscript entities: - - Approval - - EIP712DomainChanged - - Transfer + - Stats abis: - name: Harb file: ./abis/Harb.json @@ -30,3 +28,25 @@ dataSources: - event: Transfer(indexed address,indexed address,uint256) handler: handleTransfer file: ./src/harb.ts + - kind: ethereum + name: Stake + network: sepolia + source: + address: "0x4256777543814d66f9f66390f6bb33c55a24c331" + abi: Stake + startBlock: 5510935 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - Position + abis: + - name: Stake + file: ./abis/Stake.json + eventHandlers: + - event: PositionCreated(indexed uint256,indexed address,uint256,uint32,uint32) + handler: handlePositionCreated + - event: PositionRemoved(indexed uint256,uint256,uint32) + handler: handlePositionRemoved + file: ./src/stake.ts diff --git a/subgraph/harb/tests/stake-utils.ts b/subgraph/harb/tests/stake-utils.ts new file mode 100644 index 0000000..c84f8e6 --- /dev/null +++ b/subgraph/harb/tests/stake-utils.ts @@ -0,0 +1,70 @@ +import { newMockEvent } from "matchstick-as" +import { ethereum, BigInt, Address } from "@graphprotocol/graph-ts" +import { PositionCreated, PositionRemoved } from "../generated/Stake/Stake" + +export function createPositionCreatedEvent( + positionId: BigInt, + owner: Address, + share: BigInt, + creationTime: BigInt, + taxRate: BigInt +): PositionCreated { + let positionCreatedEvent = changetype(newMockEvent()) + + positionCreatedEvent.parameters = new Array() + + positionCreatedEvent.parameters.push( + new ethereum.EventParam( + "positionId", + ethereum.Value.fromUnsignedBigInt(positionId) + ) + ) + positionCreatedEvent.parameters.push( + new ethereum.EventParam("owner", ethereum.Value.fromAddress(owner)) + ) + positionCreatedEvent.parameters.push( + new ethereum.EventParam("share", ethereum.Value.fromUnsignedBigInt(share)) + ) + positionCreatedEvent.parameters.push( + new ethereum.EventParam( + "creationTime", + ethereum.Value.fromUnsignedBigInt(creationTime) + ) + ) + positionCreatedEvent.parameters.push( + new ethereum.EventParam( + "taxRate", + ethereum.Value.fromUnsignedBigInt(taxRate) + ) + ) + + return positionCreatedEvent +} + +export function createPositionRemovedEvent( + positionId: BigInt, + share: BigInt, + lastTaxTime: BigInt +): PositionRemoved { + let positionRemovedEvent = changetype(newMockEvent()) + + positionRemovedEvent.parameters = new Array() + + positionRemovedEvent.parameters.push( + new ethereum.EventParam( + "positionId", + ethereum.Value.fromUnsignedBigInt(positionId) + ) + ) + positionRemovedEvent.parameters.push( + new ethereum.EventParam("share", ethereum.Value.fromUnsignedBigInt(share)) + ) + positionRemovedEvent.parameters.push( + new ethereum.EventParam( + "lastTaxTime", + ethereum.Value.fromUnsignedBigInt(lastTaxTime) + ) + ) + + return positionRemovedEvent +} diff --git a/subgraph/harb/tests/stake.test.ts b/subgraph/harb/tests/stake.test.ts new file mode 100644 index 0000000..cac77a6 --- /dev/null +++ b/subgraph/harb/tests/stake.test.ts @@ -0,0 +1,80 @@ +import { + assert, + describe, + test, + clearStore, + beforeAll, + afterAll +} from "matchstick-as/assembly/index" +import { BigInt, Address } from "@graphprotocol/graph-ts" +import { PositionCreated } from "../generated/schema" +import { PositionCreated as PositionCreatedEvent } from "../generated/Stake/Stake" +import { handlePositionCreated } from "../src/stake" +import { createPositionCreatedEvent } from "./stake-utils" + +// Tests structure (matchstick-as >=0.5.0) +// https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0 + +describe("Describe entity assertions", () => { + beforeAll(() => { + let positionId = BigInt.fromI32(234) + let owner = Address.fromString("0x0000000000000000000000000000000000000001") + let share = BigInt.fromI32(234) + let creationTime = BigInt.fromI32(234) + let taxRate = BigInt.fromI32(234) + let newPositionCreatedEvent = createPositionCreatedEvent( + positionId, + owner, + share, + creationTime, + taxRate + ) + handlePositionCreated(newPositionCreatedEvent) + }) + + afterAll(() => { + clearStore() + }) + + // For more test scenarios, see: + // https://thegraph.com/docs/en/developer/matchstick/#write-a-unit-test + + test("PositionCreated created and stored", () => { + assert.entityCount("PositionCreated", 1) + + // 0xa16081f360e3847006db660bae1c6d1b2e17ec2a is the default address used in newMockEvent() function + assert.fieldEquals( + "PositionCreated", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "positionId", + "234" + ) + assert.fieldEquals( + "PositionCreated", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "owner", + "0x0000000000000000000000000000000000000001" + ) + assert.fieldEquals( + "PositionCreated", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "share", + "234" + ) + assert.fieldEquals( + "PositionCreated", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "creationTime", + "234" + ) + assert.fieldEquals( + "PositionCreated", + "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + "taxRate", + "234" + ) + + // More assert options: + // https://thegraph.com/docs/en/developer/matchstick/#asserts + }) +})