Skip to content

Commit

Permalink
feat: configure and store multiple treasuries (#78)
Browse files Browse the repository at this point in the history
* feat: configure and store multiple treasuries

* feat: display multiple treasuries on space page

* feat: block actions on read only treasuries

* feat: display treasuries in editor

* chore: turn executors_strategies into dummy relationship
  • Loading branch information
Sekhmet authored Mar 1, 2024
1 parent ad9181a commit 92b1844
Show file tree
Hide file tree
Showing 25 changed files with 805 additions and 465 deletions.
9 changes: 6 additions & 3 deletions apps/api/src/ipfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export async function handleSpaceMetadata(space: string, metadataUri: string) {
spaceMetadataItem.wallet = '';
spaceMetadataItem.executors = [];
spaceMetadataItem.executors_types = [];
spaceMetadataItem.treasuries = [];
spaceMetadataItem.delegations = [];

const metadata: any = metadataUri ? await getJSON(metadataUri) : {};
Expand All @@ -34,6 +35,11 @@ export async function handleSpaceMetadata(space: string, metadataUri: string) {
if (metadata.properties) {
if (metadata.properties.cover) spaceMetadataItem.cover = metadata.properties.cover;

if (metadata.properties.treasuries) {
spaceMetadataItem.treasuries = metadata.properties.treasuries.map((treasury: any) =>
JSON.stringify(treasury)
);
}
if (metadata.properties.delegations) {
spaceMetadataItem.delegations = metadata.properties.delegations.map((delegation: any) =>
JSON.stringify(delegation)
Expand All @@ -45,9 +51,6 @@ export async function handleSpaceMetadata(space: string, metadataUri: string) {
if (metadata.properties.voting_power_symbol) {
spaceMetadataItem.voting_power_symbol = metadata.properties.voting_power_symbol;
}
if (metadata.properties.wallets && metadata.properties.wallets.length > 0) {
spaceMetadataItem.wallet = metadata.properties.wallets[0];
}
if (
metadata.properties.execution_strategies &&
metadata.properties.execution_strategies_types
Expand Down
13 changes: 13 additions & 0 deletions apps/api/src/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type SpaceMetadataItem {
avatar: String!
cover: String!
external_url: String!
treasuries: [String]!
delegations: [String]!
github: String!
twitter: String!
Expand All @@ -47,6 +48,8 @@ type SpaceMetadataItem {
wallet: String!
executors: [String]!
executors_types: [String]!
# this is dummy field, we should add support for many-to-many in the future
executors_strategies: [ExecutionStrategy]! @derivedFrom(field: "id")
}

type VotingPowerValidationStrategiesParsedMetadataItem {
Expand All @@ -73,6 +76,16 @@ type StrategiesParsedMetadataDataItem {
payload: String
}

type ExecutionStrategy {
id: String!
type: String!
quorum: BigDecimalVP!
treasury: String
treasury_chain: String
timelock_veto_guardian: String
timelock_delay: BigInt!
}

type Proposal {
id: String!
proposal_id: Int!
Expand Down
4 changes: 4 additions & 0 deletions apps/subgraph-api/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type SpaceMetadata @entity {
avatar: String!
cover: String!
external_url: String!
treasuries: [String!]!
delegations: [String!]!
github: String!
twitter: String!
Expand All @@ -42,6 +43,7 @@ type SpaceMetadata @entity {
wallet: String!
executors: [Bytes!]!
executors_types: [String!]!
executors_strategies: [ExecutionStrategy!]!
}

type StrategiesParsedMetadata @entity {
Expand Down Expand Up @@ -72,6 +74,8 @@ type ExecutionStrategy @entity {
id: ID!
type: String!
quorum: BigDecimal!
treasury: Bytes
treasury_chain: Int
timelock_veto_guardian: Bytes
timelock_delay: BigInt!
}
Expand Down
25 changes: 21 additions & 4 deletions apps/subgraph-api/src/ipfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,36 @@ export function handleSpaceMetadata(content: Bytes): void {
spaceMetadata.about = description ? description.toString() : ''
spaceMetadata.avatar = avatar ? avatar.toString() : ''
spaceMetadata.external_url = externalUrl ? externalUrl.toString() : ''
spaceMetadata.wallet = ''
spaceMetadata.treasuries = []
spaceMetadata.delegations = []

if (properties) {
const propertiesObj = properties.toObject()

let treasuries = propertiesObj.get('treasuries')
let delegations = propertiesObj.get('delegations')
let cover = propertiesObj.get('cover')
let github = propertiesObj.get('github')
let twitter = propertiesObj.get('twitter')
let discord = propertiesObj.get('discord')
let votingPowerSymbol = propertiesObj.get('voting_power_symbol')
let wallets = propertiesObj.get('wallets')
let executionStrategies = propertiesObj.get('execution_strategies')
let executionStrategiesTypes = propertiesObj.get('execution_strategies_types')

if (treasuries) {
let jsonObj: JSON.Obj = <JSON.Obj>JSON.parse(content)
let jsonPropertiesObj = jsonObj.getObj('properties')
if (jsonPropertiesObj) {
let jsonTreasuriesArr = jsonPropertiesObj.getArr('treasuries')
if (jsonTreasuriesArr) {
spaceMetadata.treasuries = jsonTreasuriesArr._arr.map<string>((treasury) =>
treasury.toString()
)
}
}
}

if (delegations) {
let jsonObj: JSON.Obj = <JSON.Obj>JSON.parse(content)
let jsonPropertiesObj = jsonObj.getObj('properties')
Expand All @@ -51,8 +66,6 @@ export function handleSpaceMetadata(content: Bytes): void {
spaceMetadata.twitter = twitter ? twitter.toString() : ''
spaceMetadata.discord = discord ? discord.toString() : ''
spaceMetadata.voting_power_symbol = votingPowerSymbol ? votingPowerSymbol.toString() : 'VP'
spaceMetadata.wallet =
wallets && wallets.toArray().length > 0 ? wallets.toArray()[0].toString() : ''

if (executionStrategies && executionStrategiesTypes) {
spaceMetadata.executors = executionStrategies
Expand All @@ -61,18 +74,22 @@ export function handleSpaceMetadata(content: Bytes): void {
spaceMetadata.executors_types = executionStrategiesTypes
.toArray()
.map<string>((type) => type.toString())
spaceMetadata.executors_strategies = executionStrategies
.toArray()
.map<string>((strategy) => strategy.toString())
} else {
spaceMetadata.executors = []
spaceMetadata.executors_types = []
spaceMetadata.executors_strategies = []
}
} else {
spaceMetadata.cover = ''
spaceMetadata.github = ''
spaceMetadata.twitter = ''
spaceMetadata.discord = ''
spaceMetadata.wallet = ''
spaceMetadata.executors = []
spaceMetadata.executors_types = []
spaceMetadata.executors_strategies = []
}

spaceMetadata.save()
Expand Down
26 changes: 24 additions & 2 deletions apps/subgraph-api/src/mapping.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Address, BigDecimal, BigInt, Bytes, dataSource } from '@graphprotocol/graph-ts'
import {
Address,
BigDecimal,
BigInt,
Bytes,
DataSourceContext,
dataSource,
} from '@graphprotocol/graph-ts'
import { ProxyDeployed } from '../generated/ProxyFactory/ProxyFactory'
import { AvatarExecutionStrategy } from '../generated/ProxyFactory/AvatarExecutionStrategy'
import { TimelockExecutionStrategy } from '../generated/ProxyFactory/TimelockExecutionStrategy'
Expand Down Expand Up @@ -39,18 +46,31 @@ const MASTER_SIMPLE_QUORUM_TIMELOCK = Address.fromString(
'0xf2A1C2f2098161af98b2Cc7E382AB7F3ba86Ebc4'
)

const CHAIN_IDS = new Map<string, i32>()
CHAIN_IDS.set('mainnet', 1)
CHAIN_IDS.set('goerli', 5)
CHAIN_IDS.set('sepolia', 11155111)
CHAIN_IDS.set('matic', 137)
CHAIN_IDS.set('arbitrum-one', 42161)
CHAIN_IDS.set('linea-testnet', 59140)

export function handleProxyDeployed(event: ProxyDeployed): void {
let network = dataSource.network()

if (event.params.implementation.equals(MASTER_SPACE)) {
SpaceTemplate.create(event.params.proxy)
} else if (event.params.implementation.equals(MASTER_SIMPLE_QUORUM_AVATAR)) {
let executionStrategyContract = AvatarExecutionStrategy.bind(event.params.proxy)
let typeResult = executionStrategyContract.try_getStrategyType()
let quorumResult = executionStrategyContract.try_quorum()
if (typeResult.reverted || quorumResult.reverted) return
let targetAddress = executionStrategyContract.try_target()
if (typeResult.reverted || quorumResult.reverted || targetAddress.reverted) return

let executionStrategy = new ExecutionStrategy(event.params.proxy.toHexString())
executionStrategy.type = typeResult.value
executionStrategy.quorum = new BigDecimal(quorumResult.value)
if (CHAIN_IDS.has(network)) executionStrategy.treasury_chain = CHAIN_IDS.get(network)
executionStrategy.treasury = targetAddress.value
executionStrategy.timelock_delay = new BigInt(0)
executionStrategy.save()
} else if (event.params.implementation.equals(MASTER_SIMPLE_QUORUM_TIMELOCK)) {
Expand All @@ -71,6 +91,8 @@ export function handleProxyDeployed(event: ProxyDeployed): void {
let executionStrategy = new ExecutionStrategy(event.params.proxy.toHexString())
executionStrategy.type = typeResult.value
executionStrategy.quorum = new BigDecimal(quorumResult.value)
if (CHAIN_IDS.has(network)) executionStrategy.treasury_chain = CHAIN_IDS.get(network)
executionStrategy.treasury = event.params.proxy
executionStrategy.timelock_veto_guardian = timelockVetoGuardianResult.value
executionStrategy.timelock_delay = timelockDelayResult.value
executionStrategy.save()
Expand Down
4 changes: 1 addition & 3 deletions apps/ui/src/components/App/Nav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ const space = computed(() =>
: null
);
const { treasury } = useTreasury(space);
const isController = computed(() =>
space.value ? compareAddresses(space.value.controller, web3.value.account) : false
);
Expand All @@ -47,7 +45,7 @@ const navigationConfig = computed(() => ({
}
}
: undefined),
...(treasury.value
...(space.value?.treasuries?.length
? {
treasury: {
name: 'Treasury',
Expand Down
47 changes: 17 additions & 30 deletions apps/ui/src/components/EditorExecution.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
import Draggable from 'vuedraggable';
import { getNetwork } from '@/networks';
import { simulate } from '@/helpers/tenderly';
import { Transaction as TransactionType, Space, SelectedStrategy } from '@/types';
import { Transaction as TransactionType, Space, SpaceMetadataTreasury } from '@/types';
const model = defineModel<TransactionType[]>({
required: true
});
const props = defineProps<{
space?: Space;
selectedExecutionStrategy: SelectedStrategy;
space: Space;
treasuryData: SpaceMetadataTreasury;
}>();
const uiStore = useUiStore();
const { treasury } = useTreasury(toRef(props, 'space'));
const { treasury } = useTreasury(props.treasuryData);
const editedTx: Ref<number | null> = ref(null);
const modalState: Ref<{
Expand All @@ -31,15 +31,6 @@ const simulationState: Ref<'SIMULATING' | 'SIMULATION_SUCCEDED' | 'SIMULATION_FA
ref(null);
const network = computed(() => (props.space ? getNetwork(props.space.network) : null));
const currentTreasury = computed(() =>
props.selectedExecutionStrategy.type === 'SimpleQuorumTimelock' && network.value
? {
wallet: props.selectedExecutionStrategy.address,
network: network.value.baseChainId,
networkId: props.space?.network
}
: treasury.value
);
function addTx(tx: TransactionType) {
const newValue = [...model.value];
Expand Down Expand Up @@ -72,15 +63,11 @@ function editTx(index: number) {
}
async function handleSimulateClick() {
if (simulationState.value !== null || !currentTreasury.value) return;
if (simulationState.value !== null || !treasury.value) return;
simulationState.value = 'SIMULATING';
const valid = await simulate(
currentTreasury.value.network,
currentTreasury.value.wallet,
model.value
);
const valid = await simulate(treasury.value.network, treasury.value.wallet, model.value);
if (valid) {
simulationState.value = 'SIMULATION_SUCCEDED';
Expand All @@ -98,11 +85,11 @@ watch(model.value, () => {
<template>
<div class="space-y-3">
<div class="overflow-hidden border rounded-lg">
<ExecutionButton :disabled="!currentTreasury" @click="openModal('sendToken')">
<ExecutionButton :disabled="!treasury" @click="openModal('sendToken')">
<IH-cash class="inline-block" />
<span>Send token</span>
</ExecutionButton>
<ExecutionButton :disabled="!currentTreasury" @click="openModal('sendNft')">
<ExecutionButton :disabled="!treasury" @click="openModal('sendNft')">
<IH-photograph class="inline-block" />
<span>Send NFT</span>
</ExecutionButton>
Expand Down Expand Up @@ -168,28 +155,28 @@ watch(model.value, () => {
</div>
<teleport to="#modal">
<ModalSendToken
v-if="currentTreasury && currentTreasury.networkId"
v-if="treasury && treasury.networkId"
:open="modalOpen.sendToken"
:address="currentTreasury.wallet"
:network="currentTreasury.network"
:network-id="currentTreasury.networkId"
:address="treasury.wallet"
:network="treasury.network"
:network-id="treasury.networkId"
:initial-state="modalState.sendToken"
@close="modalOpen.sendToken = false"
@add="addTx"
/>
<ModalSendNft
v-if="currentTreasury"
v-if="treasury"
:open="modalOpen.sendNft"
:address="currentTreasury.wallet"
:network="currentTreasury.network"
:address="treasury.wallet"
:network="treasury.network"
:initial-state="modalState.sendNft"
@close="modalOpen.sendNft = false"
@add="addTx"
/>
<ModalTransaction
v-if="currentTreasury"
v-if="treasury"
:open="modalOpen.contractCall"
:network="currentTreasury.network"
:network="treasury.network"
:initial-state="modalState.contractCall"
@close="modalOpen.contractCall = false"
@add="addTx"
Expand Down
Loading

0 comments on commit 92b1844

Please sign in to comment.