Skip to content

Commit

Permalink
feat: integrate Axiom (#278)
Browse files Browse the repository at this point in the history
* chore: update Axiom integration

Co-authored-by: Wiktor Tkaczyński <wiktor.tkaczynski@gmail.com>

* feat: add Isokratia execution strategy

* feat: support cast Highlight vote

* fix: pass axiom and isokratia as execution strategies to config

* feat: finalize Axiom proposals via Mana

* feat: handle execution_ready for Axiom strategies

* fix: only show actions for Axiom proposals after max_end

---------

Co-authored-by: less <fabien@bonustrack.co>
  • Loading branch information
Sekhmet and bonustrack authored Apr 16, 2024
1 parent 5591831 commit f49adac
Show file tree
Hide file tree
Showing 29 changed files with 340 additions and 60 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-toys-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@snapshot-labs/sx": patch
---

add Axiom and Isokratia strategies to execution strategies instead of voting strategies
1 change: 1 addition & 0 deletions apps/api/src/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ type Proposal {
execution_tx: String
veto_tx: String
vote_count: Int!
execution_ready: Boolean!
executed: Boolean!
vetoed: Boolean!
completed: Boolean!
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ export const handlePropose: CheckpointWriter = async ({ block, tx, rawEvent, eve
proposal.execution_tx = null;
proposal.veto_tx = null;
proposal.vote_count = 0;
proposal.execution_ready = true;
proposal.executed = false;
proposal.vetoed = false;
proposal.completed = false;
Expand Down
2 changes: 1 addition & 1 deletion apps/mana/src/eth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getEthereumWallet, DEFAULT_INDEX, SPACES_INDICIES } from './dependencie

const jsonRpcRequestSchema = z.object({
id: z.any(),
method: z.enum(['send', 'execute', 'executeQueuedProposal']),
method: z.enum(['send', 'finalizeProposal', 'execute', 'executeQueuedProposal']),
params: z.any()
});

Expand Down
28 changes: 27 additions & 1 deletion apps/mana/src/eth/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
evmLineaGoerli,
EvmNetworkConfig
} from '@snapshot-labs/sx';
import fetch from 'cross-fetch';
import { createWalletProxy } from './dependencies';
import { rpcError, rpcSuccess } from '../utils';

Expand Down Expand Up @@ -71,6 +72,31 @@ export const createNetworkHandler = (chainId: number) => {
}
}

async function finalizeProposal(id: number, params: any, res: Response) {
try {
const { space, proposalId } = params;

const response = await fetch('http://ec2-44-197-171-215.compute-1.amazonaws.com:8000/query', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chainId,
space,
proposalId,
feeData: {
maxFeePerGas: '50000000000'
}
})
});

const result = await response.text();

return rpcSuccess(res, result, id);
} catch (e) {
return rpcError(res, 500, e, id);
}
}

async function execute(id: number, params: any, res: Response) {
try {
const { space, proposalId, executionParams } = params;
Expand Down Expand Up @@ -106,5 +132,5 @@ export const createNetworkHandler = (chainId: number) => {
}
}

return { send, execute, executeQueuedProposal };
return { send, finalizeProposal, execute, executeQueuedProposal };
};
2 changes: 1 addition & 1 deletion apps/subgraph-api/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "api-subgraph",
"private": true,
"version": "0.0.29",
"version": "0.0.31",
"scripts": {
"test": "graph test",
"codegen": "graph codegen",
Expand Down
1 change: 1 addition & 0 deletions apps/subgraph-api/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ type Proposal @entity {
execution_tx: Bytes
veto_tx: Bytes
vote_count: Int!
execution_ready: Boolean!
executed: Boolean!
vetoed: Boolean!
completed: Boolean!
Expand Down
27 changes: 26 additions & 1 deletion apps/subgraph-api/src/mapping.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Address, BigDecimal, BigInt, Bytes, dataSource } from '@graphprotocol/graph-ts'
import { ProxyDeployed } from '../generated/ProxyFactory/ProxyFactory'
import { AvatarExecutionStrategy } from '../generated/ProxyFactory/AvatarExecutionStrategy'
import { AxiomExecutionStrategy } from '../generated/ProxyFactory/AxiomExecutionStrategy'
import {
AxiomExecutionStrategy,
WriteOffchainVotes,
} from '../generated/ProxyFactory/AxiomExecutionStrategy'
import { TimelockExecutionStrategy } from '../generated/ProxyFactory/TimelockExecutionStrategy'
import {
SpaceCreated,
Expand All @@ -27,6 +30,7 @@ import {
} from '../generated/templates/TimelockExecutionStrategy/TimelockExecutionStrategy'
import {
Space as SpaceTemplate,
AxiomExecutionStrategy as AxiomExecutionStrategyTemplate,
TimelockExecutionStrategy as TimelockExecutionStrategyTemplate,
SpaceMetadata as SpaceMetadataTemplate,
ProposalMetadata as ProposalMetadataTemplate,
Expand Down Expand Up @@ -98,6 +102,8 @@ export function handleProxyDeployed(event: ProxyDeployed): void {
executionStrategy.treasury = toChecksumAddress(event.params.proxy.toHexString())
executionStrategy.timelock_delay = new BigInt(0)
executionStrategy.save()

AxiomExecutionStrategyTemplate.create(event.params.proxy)
} else if (event.params.implementation.equals(MASTER_SIMPLE_QUORUM_TIMELOCK)) {
let executionStrategyContract = TimelockExecutionStrategy.bind(event.params.proxy)
let typeResult = executionStrategyContract.try_getStrategyType()
Expand Down Expand Up @@ -232,6 +238,8 @@ export function handleProposalCreated(event: ProposalCreated): void {
proposal.execution_strategy_type = 'none'
}

proposal.execution_ready = proposal.execution_strategy_type != 'Axiom'

let executionHash = new ExecutionHash(proposal.execution_hash)
executionHash.proposal_id = proposalId
executionHash.save()
Expand Down Expand Up @@ -300,6 +308,8 @@ export function handleProposalUpdated(event: ProposalUpdated): void {
proposal.execution_strategy_type = 'none'
}

proposal.execution_ready = proposal.execution_strategy_type != 'Axiom'

let executionHash = new ExecutionHash(proposal.execution_hash)
executionHash.proposal_id = proposalId
executionHash.save()
Expand Down Expand Up @@ -623,6 +633,21 @@ export function handleProposalValidationStrategyUpdated(
space.save()
}

export function handleAxiomWriteOffchainVotes(event: WriteOffchainVotes): void {
let contract = AxiomExecutionStrategy.bind(event.address)
let spaceResult = contract.try_space()
if (spaceResult.reverted) return

let spaceId = toChecksumAddress(spaceResult.value.toHexString())

let proposal = Proposal.load(`${spaceId}/${event.params.proposalId}`)
if (!proposal) return

proposal.execution_ready = true

proposal.save()
}

export function handleTimelockProposalExecuted(event: TimelockProposalExecuted): void {
let executionHash = ExecutionHash.load(event.params.executionPayloadHash.toHexString())
if (executionHash === null) {
Expand Down
18 changes: 18 additions & 0 deletions apps/subgraph-api/subgraph.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ templates:
- event: ProposalValidationStrategyUpdated((address,bytes),string)
handler: handleProposalValidationStrategyUpdated
file: ./src/mapping.ts
- kind: ethereum
name: AxiomExecutionStrategy
network: optimism
source:
abi: AxiomExecutionStrategy
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- ProposalExecuted
abis:
- name: AxiomExecutionStrategy
file: ./abis/AxiomExecutionStrategy.json
eventHandlers:
- event: WriteOffchainVotes(uint256,uint256,uint256,uint256,uint256)
handler: handleAxiomWriteOffchainVotes
file: ./src/mapping.ts
- kind: ethereum
name: TimelockExecutionStrategy
network: optimism
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
VITE_ENABLED_NETWORKS=
VITE_MANA_URL=https://mana.pizza
VITE_HIGHLIGHT_URL=
VITE_HIGHLIGHT_URL=https://testnet.highlight.red
VITE_IPFS_GATEWAY=snapshot.4everland.link
VITE_INFURA_API_KEY=46a5dd9727bf48d4a132672d3f376146
VITE_ALCHEMY_API_KEY=ombBQyf580z-jx2EVQgJu4eTjePU-a2z
Expand Down
2 changes: 2 additions & 0 deletions apps/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
"@ethersproject/hash": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@ethersproject/units": "^5.7.0",
"@ethersproject/wallet": "^5.7.0",
"@headlessui/vue": "^1.7.16",
"@openzeppelin/merkle-tree": "^1.0.5",
"@snapshot-labs/eslint-config-vue": "^0.1.0-beta.13",
"@snapshot-labs/highlight": "^0.1.0-beta.2",
"@snapshot-labs/lock": "^0.2.0",
"@snapshot-labs/pineapple": "^1.1.0",
"@snapshot-labs/prettier-config": "^0.1.0-beta.7",
Expand Down
32 changes: 3 additions & 29 deletions apps/ui/src/components/ProposalExecutionActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,9 @@ import { Proposal as ProposalType } from '@/types';
const props = defineProps<{ proposal: ProposalType }>();
const { web3 } = useWeb3();
const {
finalizeProposal,
receiveProposal,
executeTransactions,
executeQueuedProposal,
vetoProposal
} = useActions();
const { finalizeProposal, executeTransactions, executeQueuedProposal, vetoProposal } = useActions();
const finalizeProposalSending = ref(false);
const receiveProposalSending = ref(false);
const executeTransactionsSending = ref(false);
const executeQueuedProposalSending = ref(false);
const vetoProposalSending = ref(false);
Expand Down Expand Up @@ -49,16 +42,6 @@ async function handleFinalizeProposalClick() {
}
}
async function handleReceiveProposalClick() {
receiveProposalSending.value = true;
try {
await receiveProposal(props.proposal);
} finally {
receiveProposalSending.value = false;
}
}
async function handleExecuteTransactionsClick() {
executeTransactionsSending.value = true;
Expand Down Expand Up @@ -116,7 +99,7 @@ async function handleVetoProposalClick() {
</div>
<template v-else>
<UiButton
v-if="network.hasReceive"
v-if="proposal.execution_strategy_type === 'Axiom' && !proposal.execution_ready"
class="mb-2 w-full flex justify-center items-center"
:loading="finalizeProposalSending"
@click="handleFinalizeProposalClick"
Expand All @@ -125,16 +108,7 @@ async function handleVetoProposalClick() {
Finalize proposal
</UiButton>
<UiButton
v-if="network.hasReceive"
class="mb-2 w-full flex justify-center items-center"
:loading="receiveProposalSending"
@click="handleReceiveProposalClick"
>
<IH-database class="inline-block mr-2" />
Receive proposal
</UiButton>
<UiButton
v-if="proposal.state !== 'executed'"
v-else-if="proposal.state !== 'executed'"
class="mb-2 w-full flex justify-center items-center"
:loading="executeTransactionsSending"
@click="handleExecuteTransactionsClick"
Expand Down
15 changes: 3 additions & 12 deletions apps/ui/src/composables/useActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ export function useActions() {
if (await handleCommitEnvelope(envelope, networkId)) return;

// TODO: unify send/soc to both return txHash under same property
if (envelope.signatureData || envelope.sig) {
if (envelope.payloadType === 'HIGHLIGHT_VOTE') {
console.log('Receipt', envelope.signatureData);
} else if (envelope.signatureData || envelope.sig) {
const receipt = await network.actions.send(envelope);
const hash = receipt.transaction_hash || receipt.hash;

Expand Down Expand Up @@ -340,16 +342,6 @@ export function useActions() {
await wrapPromise(proposal.network, network.actions.finalizeProposal(auth.web3, proposal));
}

async function receiveProposal(proposal: Proposal) {
if (!web3.value.account) return await forceLogin();
if (web3.value.type === 'argentx') throw new Error('ArgentX is not supported');

const network = getReadWriteNetwork(proposal.network);
if (!network.hasReceive) throw new Error('Receive on this network is not supported');

await wrapPromise('gor', network.actions.receiveProposal(auth.web3, proposal));
}

async function executeTransactions(proposal: Proposal) {
if (!web3.value.account) return await forceLogin();
if (web3.value.type === 'argentx') throw new Error('ArgentX is not supported');
Expand Down Expand Up @@ -503,7 +495,6 @@ export function useActions() {
updateProposal: wrapWithErrors(updateProposal),
cancelProposal: wrapWithErrors(cancelProposal),
finalizeProposal: wrapWithErrors(finalizeProposal),
receiveProposal: wrapWithErrors(receiveProposal),
executeTransactions: wrapWithErrors(executeTransactions),
executeQueuedProposal: wrapWithErrors(executeQueuedProposal),
vetoProposal: wrapWithErrors(vetoProposal),
Expand Down
73 changes: 73 additions & 0 deletions apps/ui/src/helpers/highlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Client } from '@snapshot-labs/highlight';
import { Wallet } from '@ethersproject/wallet';

const WALLET_PRIVATE_KEY = Wallet.createRandom().privateKey;

const HIGHLIGHT_URL = import.meta.env.VITE_HIGHLIGHT_URL || 'http://localhost:3000';

const signer = new Wallet(WALLET_PRIVATE_KEY);

export const client = new Client({
url: `${HIGHLIGHT_URL}/highlight`,
signer
});

export const baseDomain = {
name: 'snapshot-x',
version: '1.0.0'
};

type EIP712VoteMessage = {
space: string;
voter: string;
proposalId: number;
choice: number;
};

export const voteTypes = {
Vote: [
{ name: 'space', type: 'address' },
{ name: 'voter', type: 'address' },
{ name: 'proposalId', type: 'uint256' },
{ name: 'choice', type: 'uint8' }
]
};

export async function vote({ signer, data }): Promise<any> {
const voter = await signer.getAddress();

const domain = {
...baseDomain,
chainId: data.chainId,
verifyingContract: data.space
};

const message: EIP712VoteMessage = {
space: data.space,
voter,
proposalId: data.proposal,
choice: data.choice
};

const signature = await signer._signTypedData(domain, voteTypes, message);

const signatureData = {
address: voter,
signature,
domain,
types: voteTypes,
message
};

const receipt = await client.votes.vote({
space: data.space,
voter,
proposalId: data.proposal,
choice: data.choice,
chainId: data.chainId,
sig: signature
});
console.log('Receipt', receipt);

return { payloadType: 'HIGHLIGHT_VOTE', signatureData, data };
}
2 changes: 1 addition & 1 deletion apps/ui/src/helpers/mana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function registerTransaction(

export async function executionCall(
chainId: number,
method: 'execute' | 'executeQueuedProposal',
method: 'finalizeProposal' | 'execute' | 'executeQueuedProposal',
params: any
) {
return rpcCall(`eth_rpc/${chainId}`, method, params);
Expand Down
Loading

0 comments on commit f49adac

Please sign in to comment.