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

Two handlers, Tests with Mock Events and Mock Blockchain #14

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
6,932 changes: 6,728 additions & 204 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@
"typescript": "4.8.3"
},
"dependencies": {
"@typechain/ethers-v5": "^10.1.0",
"aws-sdk": "2.1216.0",
"bunyan": "^1.8.15",
"pg": "^8.8.0"
"ethers": "^5.7.1",
"pg": "^8.8.0",
"web3": "^1.8.0"
}
}
33 changes: 21 additions & 12 deletions scripts/sql/create_tables.sql
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
-- Delegatees are account that receive delegation of voting power from Delegators
CREATE TABLE delegation
(
token_address CHAR(20),
delegator_address CHAR(20),
delegatee_address CHAR(20),
network INT,
token_address CHAR(64),
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (blocking): no token_address anymore.

Copy link
Author

Choose a reason for hiding this comment

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

and no network.

delegator_address CHAR(64),
delegatee_address CHAR(64),
proof TEXT,
delegated_weight BIGINT,
total_weight BIGINT,
delegated_block BIGINT,
PRIMARY KEY (token_address,
delegator_address,
delegatee_address)
PRIMARY KEY (
network,
token_address,
delegator_address,
delegatee_address,
delegated_block)
);
CREATE INDEX delegation_index_delegator
ON delegation (
network,
token_address,
delegator_address
);

CREATE INDEX delegation_index_delegatee
ON delegation (
token_address,
delegatee_address
network,
token_address,
delegatee_address
);

-- Delegators delegate their voting power to Delegatees
CREATE TABLE delegators
(
token_address CHAR(20),
delegator_address CHAR(20),
trie_root CHAR(32),
network INT,
token_address CHAR(64),
delegator_address CHAR(64),
trie_root CHAR(128),
delegated_block BIGINT,
PRIMARY KEY (token_address, delegator_address)
total_weight INT,
PRIMARY KEY (network, token_address, delegator_address)
);
2 changes: 2 additions & 0 deletions src/abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const compLikeABI =
'[{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"address","name":"fromDelegate","type":"address"},{"indexed":true,"internalType":"address","name":"toDelegate","type":"address"}],"name":"DelegateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegate","type":"address"},{"indexed":false,"internalType":"uint256","name":"previousBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newBalance","type":"uint256"}],"name":"DelegateVotesChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"newAdmin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldPendingAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"newPendingAdmin","type":"address"}],"name":"NewPendingAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Snapshot","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DELEGATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SUPPLY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"snapshotId","type":"uint256"}],"name":"balanceOfAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"checkpoints","outputs":[{"internalType":"uint256","name":"fromBlock","type":"uint256"},{"internalType":"uint256","name":"votes","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegatee","type":"address"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegatee","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"delegateBySig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegates","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getCurrentVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getPriorVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"numCheckpoints","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newPendingAdmin","type":"address"}],"name":"setPendingAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"snapshot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"snapshotId","type":"uint256"}],"name":"totalSupplyAt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]'
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (blocking): Typechain.

Follow the standard as defined in https://github.com/windranger-io/windranger-solidity-template and use TypeChain, there should no abi files

You can use either dependencies on NPM deployable, other Git repos or local contract interface files

Copy link
Author

Choose a reason for hiding this comment

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

gotcha

72 changes: 72 additions & 0 deletions src/blockchain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {ethers} from 'ethers'
import * as abi from './abi'

/**
* Shape of the AWS Gateway event the handler has available
* https://github.com/awsdocs/aws-lambda-developer-guide/blob/main/sample-apps/nodejs-apig/event.json
*/

export const getPriorVotingPowerOf = async (
walletAddress: string,
blockNumber: number
): Promise<number> => {
const provider = new ethers.providers.InfuraProvider(
'rinkeby',
'INFURA_KEY'
)
// RINKEBY COMP-like token
const tokenContract: string =
'0xCB198597184804f175Dc7b562b0b5AF0793e9176' as string
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const contract = new ethers.Contract(
tokenContract,
abi.compLikeABI,
provider
)
const result: number = parseInt(
ethers.utils.formatEther(
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument
await contract.getPriorVotes(walletAddress, blockNumber)
)
)
return result
}

export const getCombinedVotingPowerOf = async (
walletAddress: string,
blockNumber: number
): Promise<number> => {
const provider = new ethers.providers.AlchemyProvider(
'rinkeby',
'ALCHEMY_KEY'
)
// RINKEBY COMP-like token
const tokenContract: string =
'0xCB198597184804f175Dc7b562b0b5AF0793e9176' as string
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const contract = new ethers.Contract(
tokenContract,
abi.compLikeABI,
provider
)
let result = 0
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
const delegatee: string = await contract.delegates(walletAddress)
if (delegatee === ethers.constants.AddressZero) {
result += parseInt(
ethers.utils.formatEther(
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument
await contract.balanceOf(walletAddress, {
blockTag: blockNumber
})
)
)
}
result += parseInt(
ethers.utils.formatEther(
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument
await contract.getPriorVotes(walletAddress, blockNumber)
)
)
return result
}
45 changes: 44 additions & 1 deletion test/postgres.ts → src/db/postgres.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Client, ClientConfig} from 'pg'
import {Database} from '../src/db/database'
import {Database} from './database'
import fs from 'fs'

const dbUser = 'unit_test_user'
Expand Down Expand Up @@ -58,6 +58,23 @@ async function query(dbConfig: ClientConfig, sql: string): Promise<void> {
await db.end()
}

async function queryWithParams(
dbConfig: ClientConfig,
sql: string,
params?: any[]
Copy link
Contributor

Choose a reason for hiding this comment

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

question: how does this pass linting, there's rule about not allowing any?

Copy link
Author

Choose a reason for hiding this comment

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

I added some exceptions, seems to be hard to parse the JSON in TypeScript because is polymorphic.

): Promise<void> {
let queryParams = []
if (typeof params === 'undefined') {
queryParams = []
} else {
queryParams = params
}
const db = new Client(dbConfig)
await db.connect()
await db.query(sql, queryParams)
await db.end()
}

async function createMassDelegationTables(
dbConfig: ClientConfig
): Promise<void> {
Expand All @@ -66,3 +83,29 @@ async function createMassDelegationTables(
fs.readFileSync('./scripts/sql/create_tables.sql', 'utf8')
)
}

export async function insertDelegateDb(
network: number,
tokenAddress: string,
delegator: string,
delegatee: string,
weight: number,
totalWeight: number,
blockNumber: number
): Promise<void> {
const textQuery = `INSERT INTO delegation
(network, token_address, delegator_address, delegatee_address,
proof, delegated_weight, total_weight, delegated_block)
VALUES
($1, decode($2,'hex'), decode($3,'hex'), decode($4,'hex'), $5, $6, $7, $8 ) RETURNING *`
await queryWithParams(massDelegationDB, textQuery, [
network,
tokenAddress.slice(2),
delegator.slice(2),
delegatee.slice(2),
'',
weight,
totalWeight,
blockNumber
])
}
56 changes: 45 additions & 11 deletions src/handler/get-voting-power.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,26 @@ import {
} from 'aws-lambda/trigger/api-gateway-proxy'
import {Context} from 'aws-lambda'
import {log} from '../../config/logging'
import {Database} from '../db/database'
import {throwError} from '../error'
import {getCombinedVotingPowerOf} from '../blockchain'
import {getCombinedVotingPowerOfMock} from '../../test/blockchainMock'

export const getPriorVotingPowerOfImpl = async (
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: use function syntax.

export function getPriorVotingPowerOfImpl( walletAddress: string,
    blockNumber: number,
    useMocked: boolean)::Promise<number>

Copy link
Author

Choose a reason for hiding this comment

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

ok, anon function is not very readble.

walletAddress: string,
blockNumber: number,
useMocked: boolean
): Promise<number> => {
let retValue: number
if (useMocked) {
retValue = await getCombinedVotingPowerOfMock(
walletAddress,
blockNumber
)
} else {
retValue = await getCombinedVotingPowerOf(walletAddress, blockNumber)
}
return retValue
}

/**
* Shape of the AWS Gateway event the handler has available
Expand All @@ -26,20 +44,36 @@ export const handler = async (
event.queryStringParameters ??
throwError('Missing query string parameters')

const address =
queryStringParameters.tokenAddress ??
throwError('Missing tokenAddress parameter')
const useMockedBlockchain =
queryStringParameters.useMockedBlockchain?.toLowerCase() === 'true' ||
false

const result = await Database.pool().query({
name: 'fetch-delegation-by-token-address',
text: 'SELECT * FROM delegation WHERE token_address = $1',
values: [address]
})
const reqBody = event.body || ''

const rows = result.rows
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const obj: {[id: string]: string | string[] | number} = JSON.parse(reqBody)

const addresses: string[] =
(obj.addresses as string[]) ??
(throwError('Missing body "addresses" parameter') as unknown)

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const blockNumber: number = obj.snapshot as number

const scores = []
for (const addr of addresses) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const addrDict: {[id: string]: string | number} = {address: addr}
addrDict.score = await getPriorVotingPowerOfImpl(
addr,
blockNumber,
useMockedBlockchain
)
scores.push(addrDict)
}
const resp = {score: scores}
return {
statusCode: 200,
body: `Queries: ${rows.toString()}`
body: JSON.stringify(resp)
}
}
Loading