Skip to content

Commit

Permalink
feat: add EthRelayer execution support on L2 (#270)
Browse files Browse the repository at this point in the history
* feat: index EthRelayer details including treasury data

* feat: add support for EthRelayer execution

* feat: move execution to mana

* feat: wait for L1 execution to become available

* feat: add dummy L1 execution handler

* feat: verify network when deploying L1 dependency
  • Loading branch information
Sekhmet authored May 21, 2024
1 parent c324bf9 commit b603a6e
Show file tree
Hide file tree
Showing 36 changed files with 461 additions and 137 deletions.
5 changes: 5 additions & 0 deletions .changeset/loud-suns-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@snapshot-labs/sx": patch
---

add nonce management to execute call
5 changes: 5 additions & 0 deletions .changeset/lovely-beers-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@snapshot-labs/sx": patch
---

remove deploy method from L1Executor
34 changes: 34 additions & 0 deletions apps/api/src/abis/l1/L1AvatarExectionStrategy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[
"constructor(address _owner, address _target, address _starknetCore, uint256 _executionRelayer, uint256[] _starknetSpaces, uint256 _quorum)",
"function disableSpace(uint256 space)",
"function enableSpace(uint256 space)",
"function execute(uint256 space, tuple(uint32 startTimestamp, uint32 minEndTimestamp, uint32 maxEndTimestamp, uint8 finalizationStatus, uint256 executionPayloadHash, uint256 executionStrategy, uint256 authorAddressType, uint256 author, uint256 activeVotingStrategies) proposal, uint256 votesFor, uint256 votesAgainst, uint256 votesAbstain, uint256 executionHash, tuple(address to, uint256 value, bytes data, uint8 operation, uint256 salt)[] transactions)",
"function executionRelayer() view returns (uint256)",
"function getProposalStatus(tuple(uint32 startTimestamp, uint32 minEndTimestamp, uint32 maxEndTimestamp, uint8 finalizationStatus, uint256 executionPayloadHash, uint256 executionStrategy, uint256 authorAddressType, uint256 author, uint256 activeVotingStrategies) proposal, uint256 votesFor, uint256 votesAgainst, uint256 votesAbstain) view returns (uint8)",
"function getStrategyType() pure returns (string)",
"function isSpaceEnabled(uint256 space) view returns (uint256)",
"function owner() view returns (address)",
"function quorum() view returns (uint256)",
"function renounceOwnership()",
"function setExecutionRelayer(uint256 _executionRelayer)",
"function setQuorum(uint256 _quorum)",
"function setStarknetCore(address _starknetCore)",
"function setTarget(address _target)",
"function setUp(bytes initParams)",
"function starknetCore() view returns (address)",
"function target() view returns (address)",
"function transferOwnership(address newOwner)",
"event ExecutionRelayerSet(uint256 indexed newExecutionRelayer)",
"event Initialized(uint8 version)",
"event L1AvatarExecutionStrategySetUp(address indexed _owner, address _target, address _starknetCore, uint256 _executionRelayer, uint256[] _starknetSpaces, uint256 _quorum)",
"event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)",
"event QuorumUpdated(uint256 newQuorum)",
"event SpaceDisabled(uint256 space)",
"event SpaceEnabled(uint256 space)",
"event StarknetCoreSet(address indexed newStarknetCore)",
"event TargetSet(address indexed newTarget)",
"error ExecutionFailed()",
"error InvalidPayload()",
"error InvalidProposalStatus(uint8 status)",
"error InvalidSpace()"
]
60 changes: 57 additions & 3 deletions apps/api/src/ipfs.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { validateAndParseAddress } from 'starknet';
import { Contract as EthContract } from '@ethersproject/contracts';
import { getAddress } from '@ethersproject/address';
import {
SpaceMetadataItem,
ProposalMetadataItem,
StrategiesParsedMetadataDataItem
StrategiesParsedMetadataDataItem,
ExecutionStrategy
} from '../.checkpoint/models';
import { dropIpfs, getJSON, getSpaceName } from './utils';
import L1AvatarExectionStrategyAbi from './abis/l1/L1AvatarExectionStrategy.json';
import { ethProvider, dropIpfs, getJSON, getSpaceName } from './utils';
import { networkProperties } from './overrrides';

export async function handleSpaceMetadata(space: string, metadataUri: string) {
const exists = await SpaceMetadataItem.loadEntity(dropIpfs(metadataUri));
Expand All @@ -21,7 +27,9 @@ export async function handleSpaceMetadata(space: string, metadataUri: string) {
spaceMetadataItem.voting_power_symbol = '';
spaceMetadataItem.wallet = '';
spaceMetadataItem.executors = [];
spaceMetadataItem.executors_strategies = [];
spaceMetadataItem.executors_types = [];
spaceMetadataItem.executors_destinations = [];
spaceMetadataItem.treasuries = [];
spaceMetadataItem.delegations = [];

Expand Down Expand Up @@ -55,7 +63,53 @@ export async function handleSpaceMetadata(space: string, metadataUri: string) {
metadata.properties.execution_strategies &&
metadata.properties.execution_strategies_types
) {
spaceMetadataItem.executors = metadata.properties.execution_strategies;
// In Starknet execution strategies are not always deployed via proxy (e.g. EthRelayer).
// We have to intercept it there and create single use proxy for it.
const destinations: string[] = [];
const uniqueExecutors: string[] = [];
for (let i = 0; i < metadata.properties.execution_strategies.length; i++) {
const id = crypto.randomUUID();
const destination = (metadata.properties?.execution_destinations[i] as string) ?? '';

destinations.push(destination);
uniqueExecutors.push(id);

let executionStrategy = await ExecutionStrategy.loadEntity(id);
if (!executionStrategy) executionStrategy = new ExecutionStrategy(id);

executionStrategy.type = metadata.properties.execution_strategies_types[i];
executionStrategy.address = validateAndParseAddress(
metadata.properties.execution_strategies[i]
);
executionStrategy.quorum = '0';
executionStrategy.timelock_delay = 0n;

if (executionStrategy.type === 'EthRelayer') {
const l1Destination = getAddress(destination);

const l1AvatarExecutionStrategyContract = new EthContract(
l1Destination,
L1AvatarExectionStrategyAbi,
ethProvider
);

const quorum = (await l1AvatarExecutionStrategyContract.quorum()).toBigInt();
const treasury = await l1AvatarExecutionStrategyContract.target();

executionStrategy.destination_address = l1Destination;
executionStrategy.quorum = quorum;
executionStrategy.treasury = treasury;
executionStrategy.treasury_chain = networkProperties.baseChainId;
}

await executionStrategy.save();
}

spaceMetadataItem.executors = metadata.properties.execution_strategies.map(
(strategy: string) => validateAndParseAddress(strategy)
);
spaceMetadataItem.executors_strategies = uniqueExecutors;
spaceMetadataItem.executors_destinations = destinations;
spaceMetadataItem.executors_types = metadata.properties.execution_strategies_types;
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/overrrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const createConfig = (

return {
manaRpcUrl: `${manaRpcUrl}/stark_rpc/${config.Meta.eip712ChainId}`,
baseChainId: config.Meta.herodotusAccumulatesChainId,
factoryAddress: config.Meta.spaceFactory,
propositionPowerValidationStrategyAddress: config.ProposalValidations.VotingPower,
herodotusStrategies: [
Expand Down
9 changes: 6 additions & 3 deletions apps/api/src/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,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")
executors_destinations: [String]!
executors_strategies: [ExecutionStrategy]!
}

type VotingPowerValidationStrategiesParsedMetadataItem {
Expand Down Expand Up @@ -80,10 +80,12 @@ type StrategiesParsedMetadataDataItem {

type ExecutionStrategy {
id: String!
address: String!
destination_address: String
type: String!
quorum: BigDecimalVP!
treasury: String
treasury_chain: String
treasury_chain: Int
timelock_veto_guardian: String
timelock_delay: BigInt!
axiom_snapshot_address: String
Expand All @@ -104,6 +106,7 @@ type Proposal {
execution_time: Int!
execution_strategy: String!
execution_strategy_type: String!
execution_destination: String
timelock_veto_guardian: String
timelock_delay: Int
axiom_snapshot_address: String
Expand Down
7 changes: 5 additions & 2 deletions apps/api/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ type StrategyConfig = {
params: BigNumberish[];
};

const ethProvider = new JsonRpcProvider(
process.env.L1_NETWORK_NODE_URL ?? 'https://rpc.brovider.xyz/5'
export const ethProvider = new JsonRpcProvider(
process.env.L1_NETWORK_NODE_URL ?? `https://rpc.brovider.xyz/${networkProperties.baseChainId}`
);
const starkProvider = new Provider({
rpc: {
Expand Down Expand Up @@ -128,11 +128,13 @@ export async function handleExecutionStrategy(address: string, payload: string[]
);

let quorum = 0n;
let destinationAddress = null;
if (executionStrategyType === 'SimpleQuorumVanilla') {
quorum = await executionContract.quorum();
} else if (executionStrategyType === 'EthRelayer') {
const [l1Destination] = payload;
if (!l1Destination) throw new Error('Invalid payload for EthRelayer execution strategy');
destinationAddress = l1Destination;

const SimpleQuorumExecutionStrategyContract = new EthContract(
l1Destination,
Expand All @@ -145,6 +147,7 @@ export async function handleExecutionStrategy(address: string, payload: string[]

return {
executionStrategyType,
destinationAddress,
quorum
};
} catch (e) {
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 @@ -348,6 +348,7 @@ export const handlePropose: CheckpointWriter = async ({ block, tx, rawEvent, eve
);
if (executionStrategy) {
proposal.execution_strategy_type = executionStrategy.executionStrategyType;
proposal.execution_destination = executionStrategy.destinationAddress;
proposal.quorum = executionStrategy.quorum;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/mana/src/stark/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DEFAULT_INDEX, SPACES_INDICIES, getStarknetAccount } from './dependenci

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

Expand Down
31 changes: 30 additions & 1 deletion apps/mana/src/stark/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,35 @@ export const createNetworkHandler = (chainId: string) => {
}
}

async function execute(id: number, params: any, res: Response) {
try {
const { space, proposalId, executionParams } = params;
const { account, nonceManager } = getAccount(space);

let receipt;
try {
await nonceManager.acquire();
const nonce = await nonceManager.getNonce();

receipt = await client.execute(
{
signer: account,
space,
proposalId,
executionPayload: executionParams
},
{ nonce }
);
} finally {
nonceManager.release();
}

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

async function registerTransaction(id: number, params: any, res: Response) {
try {
const { type, hash, payload } = params;
Expand Down Expand Up @@ -101,5 +130,5 @@ export const createNetworkHandler = (chainId: string) => {
}
}

return { send, registerTransaction, registerProposal, getAccount };
return { send, execute, registerTransaction, registerProposal, getAccount };
};
3 changes: 3 additions & 0 deletions apps/subgraph-api/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type SpaceMetadata @entity {
wallet: String!
executors: [String!]!
executors_types: [String!]!
executors_destinations: [String!]!
executors_strategies: [ExecutionStrategy!]!
}

Expand Down Expand Up @@ -74,6 +75,8 @@ type StrategiesParsedMetadataData @entity {

type ExecutionStrategy @entity {
id: ID!
address: String!
destination_address: String
type: String!
quorum: BigDecimal!
treasury: String
Expand Down
8 changes: 7 additions & 1 deletion apps/subgraph-api/src/ipfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function handleSpaceMetadata(content: Bytes): void {
let votingPowerSymbol = propertiesObj.get('voting_power_symbol')
let executionStrategies = propertiesObj.get('execution_strategies')
let executionStrategiesTypes = propertiesObj.get('execution_strategies_types')
let executionDestinations = propertiesObj.get('execution_destinations')

if (treasuries) {
let jsonObj: JSON.Obj = <JSON.Obj>JSON.parse(content)
Expand Down Expand Up @@ -67,7 +68,7 @@ export function handleSpaceMetadata(content: Bytes): void {
spaceMetadata.discord = discord ? discord.toString() : ''
spaceMetadata.voting_power_symbol = votingPowerSymbol ? votingPowerSymbol.toString() : 'VP'

if (executionStrategies && executionStrategiesTypes) {
if (executionStrategies && executionStrategiesTypes && executionDestinations) {
spaceMetadata.executors = executionStrategies
.toArray()
.map<string>((strategy) => toChecksumAddress(strategy.toString()))
Expand All @@ -77,9 +78,13 @@ export function handleSpaceMetadata(content: Bytes): void {
spaceMetadata.executors_strategies = executionStrategies
.toArray()
.map<string>((strategy) => toChecksumAddress(strategy.toString()))
spaceMetadata.executors_destinations = executionDestinations
.toArray()
.map<string>((destination) => destination.toString())
} else {
spaceMetadata.executors = []
spaceMetadata.executors_types = []
spaceMetadata.executors_destinations = []
spaceMetadata.executors_strategies = []
}
} else {
Expand All @@ -89,6 +94,7 @@ export function handleSpaceMetadata(content: Bytes): void {
spaceMetadata.discord = ''
spaceMetadata.executors = []
spaceMetadata.executors_types = []
spaceMetadata.executors_destinations = []
spaceMetadata.executors_strategies = []
}

Expand Down
18 changes: 9 additions & 9 deletions apps/subgraph-api/src/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ export function handleProxyDeployed(event: ProxyDeployed): void {
let targetAddress = executionStrategyContract.try_target()
if (typeResult.reverted || quorumResult.reverted || targetAddress.reverted) return

let executionStrategy = new ExecutionStrategy(
toChecksumAddress(event.params.proxy.toHexString())
)
let address = toChecksumAddress(event.params.proxy.toHexString())
let executionStrategy = new ExecutionStrategy(address)
executionStrategy.address = address
executionStrategy.type = typeResult.value
executionStrategy.quorum = new BigDecimal(quorumResult.value)
if (CHAIN_IDS.has(network)) executionStrategy.treasury_chain = CHAIN_IDS.get(network)
Expand All @@ -101,9 +101,9 @@ export function handleProxyDeployed(event: ProxyDeployed): void {
return
}

let executionStrategy = new ExecutionStrategy(
toChecksumAddress(event.params.proxy.toHexString())
)
let address = toChecksumAddress(event.params.proxy.toHexString())
let executionStrategy = new ExecutionStrategy(address)
executionStrategy.address = address
executionStrategy.type = 'Axiom' // override because contract returns AxiomExecutionStrategyMock
executionStrategy.quorum = new BigDecimal(quorumResult.value)
if (CHAIN_IDS.has(network)) executionStrategy.treasury_chain = CHAIN_IDS.get(network)
Expand Down Expand Up @@ -131,9 +131,9 @@ export function handleProxyDeployed(event: ProxyDeployed): void {
return
}

let executionStrategy = new ExecutionStrategy(
toChecksumAddress(event.params.proxy.toHexString())
)
let address = toChecksumAddress(event.params.proxy.toHexString())
let executionStrategy = new ExecutionStrategy(address)
executionStrategy.address = address
executionStrategy.type = typeResult.value
executionStrategy.quorum = new BigDecimal(quorumResult.value)
if (CHAIN_IDS.has(network)) executionStrategy.treasury_chain = CHAIN_IDS.get(network)
Expand Down
Loading

0 comments on commit b603a6e

Please sign in to comment.