diff --git a/src/clients/evm/ethereum-tx/abis/IsokratiaExecutionStrategy.json b/src/clients/evm/ethereum-tx/abis/IsokratiaExecutionStrategy.json new file mode 100644 index 0000000..41cdb90 --- /dev/null +++ b/src/clients/evm/ethereum-tx/abis/IsokratiaExecutionStrategy.json @@ -0,0 +1,66 @@ +[ + "constructor(uint32,uint256,address,address,uint256)", + "error AttemptProofExecutionNotFinalized()", + "error CannotCommitProposalBeforeStartBlock(uint32,uint32)", + "error DuplicateExecutionPayloadHash()", + "error DurationMustExceedProvingTimeAllownace(uint32,uint32,uint32)", + "error ExecutionFailed()", + "error InvalidProof()", + "error InvalidProposalStatus(uint8)", + "error InvalidSpace()", + "error IsokratiaProposalAlreadyExists(bytes32)", + "error MerkleRootAlreadyExists()", + "error MerkleRootDoesNotExist()", + "error NotIsokratiaProposal(uint256)", + "error OnlyProver()", + "error ProposalAlreadyCommitted(bytes32)", + "error ProposalAlreadyProven(bytes32)", + "error ProposalNotCommitted(bytes32)", + "error ProposalNotProcessed()", + "error ProposalNotProven(bytes32)", + "error TimelockDelayNotMet()", + "event Initialized(uint8)", + "event IsokratiaExecutionStrategySetUp(address indexed,uint32,uint256)", + "event IsokratiaSnapshotQueryAddressUpdated(address)", + "event MerkleRootSet(uint256)", + "event OwnershipTransferred(address indexed,address indexed)", + "event PostedAggregation(uint256,bytes32,uint256,uint256,uint256)", + "event ProposalCommitted(bytes32)", + "event ProposalExecuted(bytes32)", + "event ProposalProven(bytes32,bytes32)", + "event ProvingTimeAllownaceUpdated(uint32)", + "event QuorumUpdated(uint256)", + "event SnapshotAddressUpdated(address)", + "event SnapshotSlotUpdated(uint256)", + "event SpaceDisabled(address)", + "event SpaceEnabled(address)", + "event TransactionExecuted(tuple(address,uint256,bytes,uint8,uint256))", + "event TransactionProcessed(tuple(address,uint256,bytes,uint8,uint256),uint256)", + "function committedProposals(bytes32) view returns (bool, bool)", + "function disableSpace(address)", + "function enableSpace(address)", + "function execute(uint256,tuple(address,uint32,address,uint32,uint32,uint8,bytes32,uint256),uint256,uint256,uint256,bytes)", + "function executeFinalizedProposal(bytes,bytes32)", + "function getExecutionStrategyStatus(tuple(address,uint32,uint32,uint32,uint32,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,bytes32,bytes32,bytes32)) view returns (uint8)", + "function getProposalStatus(tuple(address,uint32,address,uint32,uint32,uint8,bytes32,uint256),uint256,uint256,uint256) view returns (uint8)", + "function getStrategyType() pure returns (string)", + "function isSpaceEnabled(address) view returns (uint256)", + "function isokratiaProposals(bytes32) view returns (address, uint32, uint32, uint32, uint32, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, bytes32, bytes32, bytes32)", + "function isokratiaSnapshotQueryAddress() view returns (address)", + "function owner() view returns (address)", + "function ownerAddress() view returns (address)", + "function postAggregation(bytes,bytes32,tuple(uint256[2][3],uint256[2][2][3],uint256[2][3],uint256[2][3],uint256[4][3],uint256[3],uint64[4],uint64[4],uint256,uint256,uint256,uint32,uint32,uint32))", + "function postMerkleEligibilityRoot(uint256,uint256)", + "function proposalExecutionTime(bytes32) view returns (uint256)", + "function proposalIdMerkleRoots(uint256) view returns (uint256)", + "function provingTimeAllowance() view returns (uint32)", + "function quorum() view returns (uint256)", + "function renounceOwnership()", + "function setProvingTimeAllowance(uint32)", + "function setQuorum(uint256)", + "function setUp(bytes)", + "function snapshotAddress() view returns (address)", + "function snapshotSlot() view returns (uint256)", + "function transferOwnership(address)", + "function updateSettings(tuple(address,address,uint256))" +] diff --git a/src/clients/evm/ethereum-tx/index.ts b/src/clients/evm/ethereum-tx/index.ts index 644b512..c5112d5 100644 --- a/src/clients/evm/ethereum-tx/index.ts +++ b/src/clients/evm/ethereum-tx/index.ts @@ -10,6 +10,7 @@ import ProxyFactoryAbi from './abis/ProxyFactory.json'; import AvatarExecutionStrategyAbi from './abis/AvatarExecutionStrategy.json'; import TimelockExecutionStrategyAbi from './abis/TimelockExecutionStrategy.json'; import AxiomExecutionStrategyAbi from './abis/AxiomExecutionStrategy.json'; +import IsokratiaExecutionStrategyAbi from './abis/IsokratiaExecutionStrategy.json'; import type { Signer } from '@ethersproject/abstract-signer'; import type { Propose, UpdateProposal, Vote, Envelope, AddressConfig } from '../types'; import type { EvmNetworkConfig } from '../../../types'; @@ -52,6 +53,14 @@ type AxiomExecutionStrategyParams = { querySchema: string; }; +type IsokratiaExecutionStrategyParams = { + provingTimeAllowance: number; + quorum: bigint; + queryAddress: string; + contractAddress: string; + slotIndex: bigint; +}; + type UpdateSettingsInput = { minVotingDuration?: number; maxVotingDuration?: number; @@ -224,6 +233,55 @@ export class EthereumTx { return { address, txId: response.hash }; } + async deployIsokratiaExecution({ + signer, + params: { provingTimeAllowance, quorum, queryAddress, contractAddress, slotIndex }, + saltNonce + }: { + signer: Signer; + params: IsokratiaExecutionStrategyParams; + saltNonce?: string; + }): Promise<{ txId: string; address: string }> { + saltNonce = saltNonce || `0x${randomBytes(32).toString('hex')}`; + + const implementationAddress = + this.networkConfig.executionStrategiesImplementations['Isokratia']; + + if (!implementationAddress) { + throw new Error('Missing Isokratia implementation address'); + } + + const abiCoder = new AbiCoder(); + const isokratiaExecutionStrategyInterface = new Interface(IsokratiaExecutionStrategyAbi); + const proxyFactoryContract = new Contract( + this.networkConfig.proxyFactory, + ProxyFactoryAbi, + signer + ); + + const initParams = abiCoder.encode( + ['uint32', 'uint256', 'address', 'address', 'uint256'], + [provingTimeAllowance, quorum, queryAddress, contractAddress, slotIndex] + ); + const functionData = isokratiaExecutionStrategyInterface.encodeFunctionData('setUp', [ + initParams + ]); + + const sender = await signer.getAddress(); + const salt = await this.getSalt({ + sender, + saltNonce + }); + const address = await proxyFactoryContract.predictProxyAddress(implementationAddress, salt); + const response = await proxyFactoryContract.deployProxy( + implementationAddress, + functionData, + saltNonce + ); + + return { address, txId: response.hash }; + } + async deploySpace({ signer, params: { diff --git a/src/executors/axiom.ts b/src/executors/axiom.ts index 7516395..c6fa29d 100644 --- a/src/executors/axiom.ts +++ b/src/executors/axiom.ts @@ -1,5 +1,5 @@ import { AbiCoder } from '@ethersproject/abi'; -import type { MetaTransaction } from '../utils/encoding/execution-hash'; +import type { MetaTransaction } from '../utils/encoding'; export default function createAxiomExecutor() { return { diff --git a/src/executors/index.ts b/src/executors/index.ts index 64d0019..c45e96c 100644 --- a/src/executors/index.ts +++ b/src/executors/index.ts @@ -2,6 +2,7 @@ import createVanillaExecutor from './vanilla'; import createEthRelayerExecutor from './ethRelayer'; import createAvatarExecutor from './avatar'; import createAxiomExecutor from './axiom'; +import createIsokratiaExecutor from './isokratia'; import { ExecutorType, ExecutionInput } from '../types'; export function getExecutionData( @@ -27,5 +28,9 @@ export function getExecutionData( return createAxiomExecutor().getExecutionData(executorAddress, input.transactions); } + if (type === 'Isokratia' && input?.transactions) { + return createIsokratiaExecutor().getExecutionData(executorAddress, input.transactions); + } + throw new Error(`Not enough data to create execution for executor ${executorAddress}`); } diff --git a/src/executors/isokratia.ts b/src/executors/isokratia.ts new file mode 100644 index 0000000..29caf81 --- /dev/null +++ b/src/executors/isokratia.ts @@ -0,0 +1,21 @@ +import { AbiCoder } from '@ethersproject/abi'; +import type { MetaTransaction } from '../utils/encoding'; + +export default function createIsokratiaExecutor() { + return { + type: 'isokratia', + getExecutionData(executorAddress: string, transactions: MetaTransaction[]) { + const abiCoder = new AbiCoder(); + + const executionParams = abiCoder.encode( + ['tuple(address to, uint256 value, bytes data, uint8 operation, uint256 salt)[]'], + [transactions] + ); + + return { + executor: executorAddress, + executionParams: [executionParams] + }; + } + }; +} diff --git a/src/networks.ts b/src/networks.ts index e44686b..55eef05 100644 --- a/src/networks.ts +++ b/src/networks.ts @@ -43,7 +43,8 @@ export const evmSepolia: EvmNetworkConfig = { eip712ChainId: 11155111, executionStrategiesImplementations: { ...evmGoerli.executionStrategiesImplementations, - Axiom: '0xE59405D7d40df064E85FD02a4F2F2C527172a9c1' + Axiom: '0xE59405D7d40df064E85FD02a4F2F2C527172a9c1', + Isokratia: '0xc674eCf233920aa3052738BFCDbDd0812AEE5A83' } }; diff --git a/src/types/networkConfig.ts b/src/types/networkConfig.ts index 231a12a..191f6dd 100644 --- a/src/types/networkConfig.ts +++ b/src/types/networkConfig.ts @@ -3,7 +3,8 @@ export type ExecutorType = | 'SimpleQuorumAvatar' | 'SimpleQuorumTimelock' | 'EthRelayer' - | 'Axiom'; + | 'Axiom' + | 'Isokratia'; export type VanillaAuthenticatorConfig = { type: 'vanilla';