Skip to content

Commit

Permalink
feat: add subgraph service boilerplate
Browse files Browse the repository at this point in the history
Signed-off-by: Tomás Migone <[email protected]>
  • Loading branch information
tmigone committed Mar 18, 2024
1 parent c2823b2 commit bb008af
Show file tree
Hide file tree
Showing 16 changed files with 904 additions and 13 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Set up environment
uses: ./.github/actions/setup
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ cached/
# Build artifacts
dist/
build/
typechain/
typechain-types/
deployments/hardhat/

# Ignore solc bin output
Expand All @@ -45,8 +47,10 @@ addresses-fork.json
# Keys
.keystore

# Forge artifacts
cache_forge
# Graph client
.graphclient

tx-builder-*.json
!tx-builder-template.json
!tx-builder-template.json
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "packages/subgraph-service/lib/forge-std"]
path = packages/subgraph-service/lib/forge-std
url = https://github.com/foundry-rs/forge-std
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ This repository is a Yarn workspaces monorepo containing the following packages:
| --- | --- | --- |
| [contracts](./packages/contracts) | [![npm version](https://badge.fury.io/js/@graphprotocol%2Fcontracts.svg)](https://badge.fury.io/js/@graphprotocol%2Fcontracts) | Contracts enabling the open and permissionless decentralized network known as The Graph protocol. |
| [eslint-graph-config](./packages/eslint-graph-config) | [![npm version]()]() | Shared linting and formatting rules for TypeScript projects. |
| [token-distribution](./packages/token-distribution) | - | Contracts managing token locks for network participants |
| [sdk](./packages/sdk) | [![npm version](https://badge.fury.io/js/@graphprotocol%2Fsdk.svg)](https://badge.fury.io/js/@graphprotocol%2Fsdk) | TypeScript based SDK to interact with the protocol contracts |
| [solhint-graph-config](./packages/eslint-graph-config) | [![npm version]()]() | Shared linting and formatting rules for Solidity projects. |
| [subgraph-service](./packages/subgraph-service) | [![npm version]()]() | Contracts for the Subgraph data service in Graph Horizon. |
| [token-distribution](./packages/token-distribution) | - | Contracts managing token locks for network participants |


## Development
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"packages/eslint-graph-config",
"packages/sdk",
"packages/solhint-graph-config",
"packages/subgraph-service",
"packages/token-distribution"
],
"scripts": {
Expand Down
13 changes: 13 additions & 0 deletions packages/subgraph-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Sample Hardhat Project

This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract.

Try running some of the following tasks:

```shell
npx hardhat help
npx hardhat test
REPORT_GAS=true npx hardhat test
npx hardhat node
npx hardhat run scripts/deploy.ts
```
31 changes: 31 additions & 0 deletions packages/subgraph-service/contracts/Lock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

// Uncomment this line to use console.log
// import "hardhat/console.sol";

contract Lock {
uint256 public unlockTime;
address payable public owner;

event Withdrawal(uint256 amount, uint256 when);

constructor(uint256 _unlockTime) payable {
require(block.timestamp < _unlockTime, "Unlock time should be in the future");

unlockTime = _unlockTime;
owner = payable(msg.sender);
}

function withdraw() public {
// Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal
// console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp);

require(block.timestamp >= unlockTime, "You can't withdraw yet");
require(msg.sender == owner, "You aren't the owner");

emit Withdrawal(address(this).balance, block.timestamp);

owner.transfer(address(this).balance);
}
}
12 changes: 12 additions & 0 deletions packages/subgraph-service/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @ts-check
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */

const eslintGraphConfig = require('eslint-graph-config')
module.exports = [
...eslintGraphConfig,
{
ignores: ['typechain-types/*'],
},
]
6 changes: 6 additions & 0 deletions packages/subgraph-service/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = 'contracts'
out = 'build'
libs = ['node_modules', 'lib']
test = 'test'
cache_path = 'cache_forge'
12 changes: 12 additions & 0 deletions packages/subgraph-service/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import '@nomicfoundation/hardhat-foundry'
import '@nomicfoundation/hardhat-toolbox'
import { HardhatUserConfig } from 'hardhat/config'

const config: HardhatUserConfig = {
solidity: '0.8.24',
paths: {
artifacts: './build/contracts',
},
}

export default config
1 change: 1 addition & 0 deletions packages/subgraph-service/lib/forge-std
Submodule forge-std added at ae570f
37 changes: 37 additions & 0 deletions packages/subgraph-service/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@graphprotocol/subgraph-service",
"version": "0.0.1",
"description": "",
"author": "The Graph Team",
"license": "GPL-2.0-or-later",
"scripts": {
"lint:ts": "eslint '**/*.{js,ts}' --fix",
"lint:sol": "forge fmt",
"lint": "yarn lint:ts && yarn lint:sol",
"clean": "rm -rf build cache typechain-types",
"build": "hardhat compile"
},
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
"@nomicfoundation/hardhat-ethers": "^3.0.0",
"@nomicfoundation/hardhat-foundry": "^1.1.1",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
"@types/mocha": ">=9.1.0",
"@types/node": ">=16.0.0",
"chai": "^4.2.0",
"eslint": "^8.56.0",
"eslint-graph-config": "workspace:^0.0.1",
"ethers": "^6.4.0",
"hardhat": "^2.20.1",
"hardhat-gas-reporter": "^1.0.8",
"solidity-coverage": "^0.8.0",
"ts-node": ">=8.0.0",
"typechain": "^8.3.0",
"typescript": "^5.3.3"
}
}
28 changes: 28 additions & 0 deletions packages/subgraph-service/scripts/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ethers } from 'hardhat'

async function main() {
const currentTimestampInSeconds = Math.round(Date.now() / 1000)
const unlockTime = currentTimestampInSeconds + 60

const lockedAmount = ethers.parseEther('0.001')
const a = 1
console.log(a)
const lock = await ethers.deployContract('Lock', [unlockTime], {
value: lockedAmount,
})

await lock.waitForDeployment()

console.log(
`Lock with ${ethers.formatEther(
lockedAmount,
)}ETH and unlock timestamp ${unlockTime} deployed to ${typeof lock.target == 'string' ? lock.target : ''}`,
)
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
129 changes: 129 additions & 0 deletions packages/subgraph-service/test/Lock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {
loadFixture,
time,
} from '@nomicfoundation/hardhat-toolbox/network-helpers'
import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'
import { expect } from 'chai'
import hardhat from 'hardhat'

const ethers = hardhat.ethers

describe('Lock', function () {
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,
// and reset Hardhat Network to that snapshot in every test.
async function deployOneYearLockFixture() {
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60
const ONE_GWEI = 1_000_000_000

const lockedAmount = ONE_GWEI
const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS

// Contracts are deployed using the first signer/account by default
const [owner, otherAccount] = await ethers.getSigners()

const Lock = await ethers.getContractFactory('Lock')
const lock = await Lock.deploy(unlockTime, { value: lockedAmount })

return { lock, unlockTime, lockedAmount, owner, otherAccount }
}

describe('Deployment', function () {
it('Should set the right unlockTime', async function () {
const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture)

expect(await lock.unlockTime()).to.equal(unlockTime)
})

it('Should set the right owner', async function () {
const { lock, owner } = await loadFixture(deployOneYearLockFixture)

expect(await lock.owner()).to.equal(owner.address)
})

it('Should receive and store the funds to lock', async function () {
const { lock, lockedAmount } = await loadFixture(
deployOneYearLockFixture,
)

expect(await ethers.provider.getBalance(lock.target)).to.equal(
lockedAmount,
)
})

it('Should fail if the unlockTime is not in the future', async function () {
// We don't use the fixture here because we want a different deployment
const latestTime = await time.latest()
const Lock = await ethers.getContractFactory('Lock')
await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
'Unlock time should be in the future',
)
})
})

describe('Withdrawals', function () {
describe('Validations', function () {
it('Should revert with the right error if called too soon', async function () {
const { lock } = await loadFixture(deployOneYearLockFixture)

await expect(lock.withdraw()).to.be.revertedWith(
'You can\'t withdraw yet',
)
})

it('Should revert with the right error if called from another account', async function () {
const { lock, unlockTime, otherAccount } = await loadFixture(
deployOneYearLockFixture,
)

// We can increase the time in Hardhat Network
await time.increaseTo(unlockTime)

// We use lock.connect() to send a transaction from another account
await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
'You aren\'t the owner',
)
})

it('Shouldn\'t fail if the unlockTime has arrived and the owner calls it', async function () {
const { lock, unlockTime } = await loadFixture(
deployOneYearLockFixture,
)

// Transactions are sent using the first signer by default
await time.increaseTo(unlockTime)

await expect(lock.withdraw()).not.to.be.reverted
})
})

describe('Events', function () {
it('Should emit an event on withdrawals', async function () {
const { lock, unlockTime, lockedAmount } = await loadFixture(
deployOneYearLockFixture,
)

await time.increaseTo(unlockTime)

await expect(lock.withdraw())
.to.emit(lock, 'Withdrawal')
.withArgs(lockedAmount, anyValue) // We accept any value as `when` arg
})
})

describe('Transfers', function () {
it('Should transfer the funds to the owner', async function () {
const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
deployOneYearLockFixture,
)

await time.increaseTo(unlockTime)

await expect(lock.withdraw()).to.changeEtherBalances(
[owner, lock],
[lockedAmount, -lockedAmount],
)
})
})
})
})
17 changes: 17 additions & 0 deletions packages/subgraph-service/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": [
"hardhat.config.ts",
"scripts/**/*.ts",
"test/**/*.ts",
"eslint.config.js"
]
}
Loading

0 comments on commit bb008af

Please sign in to comment.