From c6b5ab5fbd9f38667668c7c6b7da340aa5ca762c Mon Sep 17 00:00:00 2001 From: Bartek Rutkowski Date: Sun, 10 Nov 2024 04:54:52 +0100 Subject: [PATCH 1/5] feat: add base governance executor --- abis/baseGovernanceExecutor.json | 704 ++++++++++++++++++ abis/index.ts | 1 + contracts/MockReceiver.sol | 41 + hardhat.config.ts | 6 + package.json | 3 +- .../governance-executor-base.json | 14 + .../base/governance-executor.test.ts | 225 ++++++ .../gnosis/governance-executor.test.ts | 2 +- utils/addresses.ts | 4 + web3-functions/governance-executor/index.ts | 25 +- 10 files changed, 1017 insertions(+), 8 deletions(-) create mode 100644 abis/baseGovernanceExecutor.json create mode 100644 contracts/MockReceiver.sol create mode 100644 scripts/configs/governance-executor/governance-executor-base.json create mode 100644 test/web3-functions/base/governance-executor.test.ts diff --git a/abis/baseGovernanceExecutor.json b/abis/baseGovernanceExecutor.json new file mode 100644 index 0000000..78a1525 --- /dev/null +++ b/abis/baseGovernanceExecutor.json @@ -0,0 +1,704 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "delay_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gracePeriod_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyTargets", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [], + "name": "GracePeriodTooShort", + "type": "error" + }, + { + "inputs": [], + "name": "InconsistentParamsLength", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidActionsSetId", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyQueuedActions", + "type": "error" + }, + { + "inputs": [], + "name": "TimelockNotFinished", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "ActionsSetCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "initiatorExecution", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "returnedData", + "type": "bytes[]" + } + ], + "name": "ActionsSetExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "string[]", + "name": "signatures", + "type": "string[]" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "bool[]", + "name": "withDelegatecalls", + "type": "bool[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "executionTime", + "type": "uint256" + } + ], + "name": "ActionsSetQueued", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldDelay", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newDelay", + "type": "uint256" + } + ], + "name": "DelayUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldGracePeriod", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newGracePeriod", + "type": "uint256" + } + ], + "name": "GracePeriodUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GUARDIAN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MINIMUM_GRACE_PERIOD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SUBMISSION_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "actionsSetCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "actionsSetId", + "type": "uint256" + } + ], + "name": "cancel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "delay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "actionsSetId", + "type": "uint256" + } + ], + "name": "execute", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "executeDelegateCall", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "actionsSetId", + "type": "uint256" + } + ], + "name": "getActionsSetById", + "outputs": [ + { + "components": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "string[]", + "name": "signatures", + "type": "string[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bool[]", + "name": "withDelegatecalls", + "type": "bool[]" + }, + { + "internalType": "uint256", + "name": "executionTime", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "executed", + "type": "bool" + }, + { + "internalType": "bool", + "name": "canceled", + "type": "bool" + } + ], + "internalType": "struct IExecutor.ActionsSet", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "actionsSetId", + "type": "uint256" + } + ], + "name": "getCurrentState", + "outputs": [ + { + "internalType": "enum IExecutor.ActionsSetState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gracePeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "string[]", + "name": "signatures", + "type": "string[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bool[]", + "name": "withDelegatecalls", + "type": "bool[]" + } + ], + "name": "queue", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "receiveFunds", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newDelay", + "type": "uint256" + } + ], + "name": "updateDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newGracePeriod", + "type": "uint256" + } + ], + "name": "updateGracePeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abis/index.ts b/abis/index.ts index 928a496..3b0752d 100644 --- a/abis/index.ts +++ b/abis/index.ts @@ -1,3 +1,4 @@ +export { default as baseGovernanceExecutorAbi } from './baseGovernanceExecutor.json' export { default as capAutomatorAbi } from './capAutomator.json' export { default as d3mHubAbi } from './d3mHub.json' export { default as dsNoteAbi } from './dsNote.json' diff --git a/contracts/MockReceiver.sol b/contracts/MockReceiver.sol new file mode 100644 index 0000000..3185dd3 --- /dev/null +++ b/contracts/MockReceiver.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.10; + +interface IExecutor { + function queue( + address[] memory targets, + uint256[] memory values, + string[] memory signatures, + bytes[] memory calldatas, + bool[] memory withDelegatecalls + ) external; +} + +contract MockReceiver { + address public executor; + + constructor(address _executor) { + executor = _executor; + } + + function __callQueueOnExecutor(address _payload) public { + address[] memory targets = new address[](1); + targets[0] = address(_payload); + uint256[] memory values = new uint256[](1); + values[0] = 0; + string[] memory signatures = new string[](1); + signatures[0] = "execute()"; + bytes[] memory calldatas = new bytes[](1); + calldatas[0] = ""; + bool[] memory withDelegatecalls = new bool[](1); + withDelegatecalls[0] = true; + + IExecutor(executor).queue( + targets, + values, + signatures, + calldatas, + withDelegatecalls + ); + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 00ea010..7b4d10a 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -42,6 +42,12 @@ if (TEST_NETWORK == 'mainnet') { url: `https://rpc.ankr.com/gnosis`, blockNumber: 33991690, // 2024-05-17 01:39:20 } +} else if (TEST_NETWORK == 'base') { + console.log('\nRunning Base Chain test suite') + forking = { + url: `https://base-mainnet.g.alchemy.com/v2/${ALCHEMY_ID}`, + blockNumber: 22209900, // 2024-11-10 03:25:47 + } } // ================================= CONFIG ========================================= diff --git a/package.json b/package.json index 005dce9..b62ee55 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,10 @@ "lint:fix": "yarn lint --fix", "lint:sol": "solhint contracts/**/*.sol", "test:mainnet": "export TEST_NETWORK=\"mainnet\" && npx hardhat test test/web3-functions/mainnet/*", + "test:base": "export TEST_NETWORK=\"base\" && npx hardhat test test/web3-functions/base/*", "test:gnosis": "export TEST_NETWORK=\"gnosis\" && npx hardhat test test/web3-functions/gnosis/*", "test:utils": "npx hardhat test test/utils/*", - "test": "yarn test:utils && yarn test:gnosis && yarn test:mainnet", + "test": "yarn test:utils && yarn test:base && yarn test:gnosis && yarn test:mainnet", "verify": "npx hardhat etherscan-verify", "w3f:test": "npx w3f test web3-functions/simple/index.ts --logs --chain-id=88153591557" }, diff --git a/scripts/configs/governance-executor/governance-executor-base.json b/scripts/configs/governance-executor/governance-executor-base.json new file mode 100644 index 0000000..0dfcf08 --- /dev/null +++ b/scripts/configs/governance-executor/governance-executor-base.json @@ -0,0 +1,14 @@ +{ + "domain": "base", + "args": { + "domain": "base", + "sendSlackMessages": true + }, + "secrets": { + "SLACK_WEBHOOK_URL": "SPARK_INFO_SLACK_WEBHOOK_URL" + }, + "trigger": { + "type": "time", + "interval": "3600000" + } +} diff --git a/test/web3-functions/base/governance-executor.test.ts b/test/web3-functions/base/governance-executor.test.ts new file mode 100644 index 0000000..5afa9e4 --- /dev/null +++ b/test/web3-functions/base/governance-executor.test.ts @@ -0,0 +1,225 @@ +import hre from 'hardhat' +import chai from 'chai' +import { Contract, ContractFactory } from '@ethersproject/contracts' +import { Web3FunctionHardhat } from '@gelatonetwork/web3-functions-sdk/hardhat-plugin' +import { Web3FunctionResultCallData } from '@gelatonetwork/web3-functions-sdk/*' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { + SnapshotRestorer, + impersonateAccount, + mine, + setBalance, + takeSnapshot, +} from '@nomicfoundation/hardhat-network-helpers' +import { baseGovernanceExecutorAbi } from '../../../abis' +import { addresses } from '../../../utils' + +const { w3f, ethers } = hre +const { expect } = chai + +describe('GovernanceExecutor', function () { + this.timeout(0) + + let cleanStateRestorer: SnapshotRestorer + let snapshotRestorer: SnapshotRestorer + + let governanceExecutorW3F: Web3FunctionHardhat + let keeper: SignerWithAddress + + let payloadFactory: ContractFactory + + let executor: Contract + let mockReceiver: Contract + + let executorAddress: string + let executionDelay: number + + const userArgs = { domain: 'base', sendSlackMessages: false } + + before(async () => { + cleanStateRestorer = await takeSnapshot() + ;[keeper] = await ethers.getSigners() + + executorAddress = addresses.base.executor + const mockReceiverFactory = await ethers.getContractFactory('MockReceiver') + mockReceiver = await mockReceiverFactory.deploy(executorAddress) + + await setBalance(executorAddress, ethers.utils.parseEther('1')) + await impersonateAccount(executorAddress) + const executorSigner = await ethers.getSigner(executorAddress) + executor = new Contract(executorAddress, baseGovernanceExecutorAbi, executorSigner) + const submissionRole = await executor.SUBMISSION_ROLE() + await executor.grantRole(submissionRole, mockReceiver.address) + executionDelay = Number(await executor.delay()) + + payloadFactory = await ethers.getContractFactory('EmptyPayload') + + governanceExecutorW3F = w3f.get('governance-executor') + }) + + beforeEach(async () => { + snapshotRestorer = await takeSnapshot() + }) + + afterEach(async () => { + await snapshotRestorer.restore() + }) + + after(async () => { + await cleanStateRestorer.restore() + }) + + it('no actions to execute', async () => { + const { result } = await governanceExecutorW3F.run('onRun', { userArgs }) + + expect(result.canExec).to.equal(false) + !result.canExec && expect(result.message).to.equal('No actions to execute') + }) + + it('fails when domain is invalid', async () => { + const { result } = await governanceExecutorW3F.run('onRun', { + userArgs: { domain: 'invalid-domain', sendSlackMessages: false }, + }) + + expect(result.canExec).to.equal(false) + !result.canExec && expect(result.message).to.equal('Invalid domain: invalid-domain') + }) + + it('does not execute actions when no actions are ready', async () => { + const payload = await payloadFactory.deploy() + await mockReceiver.__callQueueOnExecutor(payload.address) + + const { result } = await governanceExecutorW3F.run('onRun', { userArgs }) + + expect(result.canExec).to.equal(false) + !result.canExec && expect(result.message).to.equal('No actions to execute') + }) + + it('executes single proposal', async () => { + const payload = await payloadFactory.deploy() + await mockReceiver.__callQueueOnExecutor(payload.address) + + await mine(2, { interval: executionDelay - 1 }) + + const { result: negativeResult } = await governanceExecutorW3F.run('onRun', { userArgs }) + expect(negativeResult.canExec).to.equal(false) + + await mine(2, { interval: 1 }) + + const { result: positiveResult } = await governanceExecutorW3F.run('onRun', { userArgs }) + + expect(positiveResult.canExec).to.equal(true) + + if (!positiveResult.canExec) { + throw '' + } + const callData = positiveResult.callData as Web3FunctionResultCallData[] + + expect(callData).to.deep.equal([ + { + to: executorAddress, + data: executor.interface.encodeFunctionData('execute', [0]), + }, + ]) + + expect(await executor.getCurrentState(0)).to.equal(0) + + await keeper.sendTransaction({ + to: callData[0].to, + data: callData[0].data, + }) + + expect(await executor.getCurrentState(0)).to.equal(1) + }) + + it('executes single proposal (second in timelock)', async () => { + const firstPayload = await payloadFactory.deploy() + const secondPayload = await payloadFactory.deploy() + + await mockReceiver.__callQueueOnExecutor(firstPayload.address) + + await mine(2, { interval: executionDelay - 1 }) + + await mockReceiver.__callQueueOnExecutor(secondPayload.address) + + const { result: negativeResult } = await governanceExecutorW3F.run('onRun', { userArgs }) + expect(negativeResult.canExec).to.equal(false) + + await mine(2, { interval: 1 }) + + const { result: positiveResult } = await governanceExecutorW3F.run('onRun', { userArgs }) + + expect(positiveResult.canExec).to.equal(true) + + if (!positiveResult.canExec) { + throw '' + } + const callData = positiveResult.callData as Web3FunctionResultCallData[] + + expect(callData).to.deep.equal([ + { + to: executorAddress, + data: executor.interface.encodeFunctionData('execute', [0]), + }, + ]) + + expect(await executor.getCurrentState(0)).to.equal(0) + + await keeper.sendTransaction({ + to: callData[0].to, + data: callData[0].data, + }) + + expect(await executor.getCurrentState(0)).to.equal(1) + }) + + it('executes multiple proposals', async () => { + const firstPayload = await payloadFactory.deploy() + const secondPayload = await payloadFactory.deploy() + + await mockReceiver.__callQueueOnExecutor(firstPayload.address) + await mockReceiver.__callQueueOnExecutor(secondPayload.address) + + await mine(2, { interval: executionDelay - 1 }) + + const { result: negativeResult } = await governanceExecutorW3F.run('onRun', { userArgs }) + expect(negativeResult.canExec).to.equal(false) + + await mine(2, { interval: 1 }) + + const { result: positiveResult } = await governanceExecutorW3F.run('onRun', { userArgs }) + + expect(positiveResult.canExec).to.equal(true) + + if (!positiveResult.canExec) { + throw '' + } + const callData = positiveResult.callData as Web3FunctionResultCallData[] + + expect(callData).to.deep.equal([ + { + to: executorAddress, + data: executor.interface.encodeFunctionData('execute', [0]), + }, + { + to: executorAddress, + data: executor.interface.encodeFunctionData('execute', [1]), + }, + ]) + + expect(await executor.getCurrentState(0)).to.equal(0) + expect(await executor.getCurrentState(1)).to.equal(0) + + await keeper.sendTransaction({ + to: callData[0].to, + data: callData[0].data, + }) + await keeper.sendTransaction({ + to: callData[1].to, + data: callData[1].data, + }) + + expect(await executor.getCurrentState(0)).to.equal(1) + expect(await executor.getCurrentState(1)).to.equal(1) + }) +}) diff --git a/test/web3-functions/gnosis/governance-executor.test.ts b/test/web3-functions/gnosis/governance-executor.test.ts index 2099812..f236b6b 100644 --- a/test/web3-functions/gnosis/governance-executor.test.ts +++ b/test/web3-functions/gnosis/governance-executor.test.ts @@ -12,7 +12,7 @@ import { takeSnapshot, } from '@nomicfoundation/hardhat-network-helpers' import { gnosisGovernanceExecutorAbi } from '../../../abis' -import { addresses, sendMessageToSlack } from '../../../utils' +import { addresses } from '../../../utils' const { w3f, ethers } = hre const { expect } = chai diff --git a/utils/addresses.ts b/utils/addresses.ts index 43edd2f..2b02483 100644 --- a/utils/addresses.ts +++ b/utils/addresses.ts @@ -43,4 +43,8 @@ export const addresses = { executor: '0xc4218C1127cB24a0D6c1e7D25dc34e10f2625f5A', multicall: '0xcA11bde05977b3631167028862bE2a173976CA11', }, + base: { + executor: '0xF93B7122450A50AF3e5A76E1d546e95Ac1d0F579', + multicall: '0xcA11bde05977b3631167028862bE2a173976CA11', + }, } as const diff --git a/web3-functions/governance-executor/index.ts b/web3-functions/governance-executor/index.ts index f250aa5..db6e7aa 100644 --- a/web3-functions/governance-executor/index.ts +++ b/web3-functions/governance-executor/index.ts @@ -2,10 +2,10 @@ import { Contract } from '@ethersproject/contracts' import { Web3Function, Web3FunctionContext } from '@gelatonetwork/web3-functions-sdk' import axios from 'axios' -import { multicallAbi, remoteExecutorAbi } from '../../abis' +import { multicallAbi, gnosisGovernanceExecutorAbi, baseGovernanceExecutorAbi } from '../../abis' import { addresses, sendMessageToSlack } from '../../utils' -const foreignDomainAliases = ['gnosis'] as const +const foreignDomainAliases = ['gnosis', 'base'] as const type ForeignDomainAlias = (typeof foreignDomainAliases)[number] Web3Function.onRun(async (context: Web3FunctionContext) => { @@ -14,23 +14,36 @@ Web3Function.onRun(async (context: Web3FunctionContext) => { const domain = userArgs.domain as ForeignDomainAlias const sendSlackMessages = userArgs.sendSlackMessages as boolean - const slackWebhookUrl = (await secrets.get('SLACK_WEBHOOK_URL')) as string - - if (foreignDomainAliases.indexOf(domain) === -1) { + let remoteExecutorAbi + if (domain === 'gnosis') { + remoteExecutorAbi = gnosisGovernanceExecutorAbi + } else if (domain === 'base') { + remoteExecutorAbi = baseGovernanceExecutorAbi + } else { return { canExec: false, message: `Invalid domain: ${domain}`, } } + + const slackWebhookUrl = (await secrets.get('SLACK_WEBHOOK_URL')) as string + const executorAddress = addresses[domain].executor const multicallAddress = addresses[domain].multicall const provider = multiChainProvider.default() + const executor = new Contract(executorAddress, remoteExecutorAbi, provider) const multicall = new Contract(multicallAddress, multicallAbi, provider) - const actionSetCount = BigInt(await executor.getActionsSetCount()) + let actionSetCount = 0 + if (domain === 'gnosis') { + actionSetCount = Number(await executor.getActionsSetCount()) + } + if (domain === 'base') { + actionSetCount = Number(await executor.actionsSetCount()) + } const latestBlockTimestamp = (await provider.getBlock('latest')).timestamp From 6d5cdd225b0708fd45129ee255fa6a87092fd3e0 Mon Sep 17 00:00:00 2001 From: Bartek Rutkowski Date: Sun, 10 Nov 2024 04:57:50 +0100 Subject: [PATCH 2/5] refactor: minor reorg --- web3-functions/governance-executor/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/web3-functions/governance-executor/index.ts b/web3-functions/governance-executor/index.ts index db6e7aa..72b35a0 100644 --- a/web3-functions/governance-executor/index.ts +++ b/web3-functions/governance-executor/index.ts @@ -14,6 +14,8 @@ Web3Function.onRun(async (context: Web3FunctionContext) => { const domain = userArgs.domain as ForeignDomainAlias const sendSlackMessages = userArgs.sendSlackMessages as boolean + const slackWebhookUrl = (await secrets.get('SLACK_WEBHOOK_URL')) as string + let remoteExecutorAbi if (domain === 'gnosis') { remoteExecutorAbi = gnosisGovernanceExecutorAbi @@ -26,14 +28,11 @@ Web3Function.onRun(async (context: Web3FunctionContext) => { } } - const slackWebhookUrl = (await secrets.get('SLACK_WEBHOOK_URL')) as string - const executorAddress = addresses[domain].executor const multicallAddress = addresses[domain].multicall const provider = multiChainProvider.default() - const executor = new Contract(executorAddress, remoteExecutorAbi, provider) const multicall = new Contract(multicallAddress, multicallAbi, provider) From 5edf91a04edc969d5a87ee42ede36705e13cdeaf Mon Sep 17 00:00:00 2001 From: Bartek Rutkowski Date: Sun, 10 Nov 2024 05:04:05 +0100 Subject: [PATCH 3/5] refactor: reorg --- web3-functions/governance-executor/index.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web3-functions/governance-executor/index.ts b/web3-functions/governance-executor/index.ts index 72b35a0..069f313 100644 --- a/web3-functions/governance-executor/index.ts +++ b/web3-functions/governance-executor/index.ts @@ -2,7 +2,7 @@ import { Contract } from '@ethersproject/contracts' import { Web3Function, Web3FunctionContext } from '@gelatonetwork/web3-functions-sdk' import axios from 'axios' -import { multicallAbi, gnosisGovernanceExecutorAbi, baseGovernanceExecutorAbi } from '../../abis' +import { baseGovernanceExecutorAbi,gnosisGovernanceExecutorAbi, multicallAbi } from '../../abis' import { addresses, sendMessageToSlack } from '../../utils' const foreignDomainAliases = ['gnosis', 'base'] as const @@ -11,23 +11,23 @@ type ForeignDomainAlias = (typeof foreignDomainAliases)[number] Web3Function.onRun(async (context: Web3FunctionContext) => { const { multiChainProvider, userArgs, secrets } = context - const domain = userArgs.domain as ForeignDomainAlias - const sendSlackMessages = userArgs.sendSlackMessages as boolean - - const slackWebhookUrl = (await secrets.get('SLACK_WEBHOOK_URL')) as string - let remoteExecutorAbi - if (domain === 'gnosis') { + if (userArgs.domain === 'gnosis') { remoteExecutorAbi = gnosisGovernanceExecutorAbi - } else if (domain === 'base') { + } else if (userArgs.domain === 'base') { remoteExecutorAbi = baseGovernanceExecutorAbi } else { return { canExec: false, - message: `Invalid domain: ${domain}`, + message: `Invalid domain: ${userArgs.domain}`, } } + const domain = userArgs.domain as ForeignDomainAlias + const sendSlackMessages = userArgs.sendSlackMessages as boolean + + const slackWebhookUrl = (await secrets.get('SLACK_WEBHOOK_URL')) as string + const executorAddress = addresses[domain].executor const multicallAddress = addresses[domain].multicall From ade71f81619eddb5621a0376b5fb5e4ab29b39e9 Mon Sep 17 00:00:00 2001 From: Bartek Rutkowski Date: Sun, 10 Nov 2024 05:09:09 +0100 Subject: [PATCH 4/5] fix: format --- web3-functions/governance-executor/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web3-functions/governance-executor/index.ts b/web3-functions/governance-executor/index.ts index 069f313..1aa96c3 100644 --- a/web3-functions/governance-executor/index.ts +++ b/web3-functions/governance-executor/index.ts @@ -2,7 +2,7 @@ import { Contract } from '@ethersproject/contracts' import { Web3Function, Web3FunctionContext } from '@gelatonetwork/web3-functions-sdk' import axios from 'axios' -import { baseGovernanceExecutorAbi,gnosisGovernanceExecutorAbi, multicallAbi } from '../../abis' +import { baseGovernanceExecutorAbi, gnosisGovernanceExecutorAbi, multicallAbi } from '../../abis' import { addresses, sendMessageToSlack } from '../../utils' const foreignDomainAliases = ['gnosis', 'base'] as const From 08794942b8595a617330e4f56bf97dd8cf1b8141 Mon Sep 17 00:00:00 2001 From: Bartek Rutkowski Date: Sun, 10 Nov 2024 05:16:32 +0100 Subject: [PATCH 5/5] test: relax time edge case --- test/web3-functions/base/governance-executor.test.ts | 12 ++++++------ .../gnosis/governance-executor.test.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/web3-functions/base/governance-executor.test.ts b/test/web3-functions/base/governance-executor.test.ts index 5afa9e4..62f21cc 100644 --- a/test/web3-functions/base/governance-executor.test.ts +++ b/test/web3-functions/base/governance-executor.test.ts @@ -99,12 +99,12 @@ describe('GovernanceExecutor', function () { const payload = await payloadFactory.deploy() await mockReceiver.__callQueueOnExecutor(payload.address) - await mine(2, { interval: executionDelay - 1 }) + await mine(2, { interval: executionDelay - 2 }) const { result: negativeResult } = await governanceExecutorW3F.run('onRun', { userArgs }) expect(negativeResult.canExec).to.equal(false) - await mine(2, { interval: 1 }) + await mine(2, { interval: 2 }) const { result: positiveResult } = await governanceExecutorW3F.run('onRun', { userArgs }) @@ -138,14 +138,14 @@ describe('GovernanceExecutor', function () { await mockReceiver.__callQueueOnExecutor(firstPayload.address) - await mine(2, { interval: executionDelay - 1 }) + await mine(2, { interval: executionDelay - 2 }) await mockReceiver.__callQueueOnExecutor(secondPayload.address) const { result: negativeResult } = await governanceExecutorW3F.run('onRun', { userArgs }) expect(negativeResult.canExec).to.equal(false) - await mine(2, { interval: 1 }) + await mine(2, { interval: 2 }) const { result: positiveResult } = await governanceExecutorW3F.run('onRun', { userArgs }) @@ -180,12 +180,12 @@ describe('GovernanceExecutor', function () { await mockReceiver.__callQueueOnExecutor(firstPayload.address) await mockReceiver.__callQueueOnExecutor(secondPayload.address) - await mine(2, { interval: executionDelay - 1 }) + await mine(2, { interval: executionDelay - 2 }) const { result: negativeResult } = await governanceExecutorW3F.run('onRun', { userArgs }) expect(negativeResult.canExec).to.equal(false) - await mine(2, { interval: 1 }) + await mine(2, { interval: 2 }) const { result: positiveResult } = await governanceExecutorW3F.run('onRun', { userArgs }) diff --git a/test/web3-functions/gnosis/governance-executor.test.ts b/test/web3-functions/gnosis/governance-executor.test.ts index f236b6b..ae21cce 100644 --- a/test/web3-functions/gnosis/governance-executor.test.ts +++ b/test/web3-functions/gnosis/governance-executor.test.ts @@ -97,12 +97,12 @@ describe('GovernanceExecutor', function () { const payload = await payloadFactory.deploy() await mockAMB.__callQueueOnExecutor(payload.address) - await mine(2, { interval: executionDelay - 1 }) + await mine(2, { interval: executionDelay - 2 }) const { result: negativeResult } = await governanceExecutorW3F.run('onRun') expect(negativeResult.canExec).to.equal(false) - await mine(2, { interval: 1 }) + await mine(2, { interval: 2 }) const { result: positiveResult } = await governanceExecutorW3F.run('onRun') @@ -136,14 +136,14 @@ describe('GovernanceExecutor', function () { await mockAMB.__callQueueOnExecutor(firstPayload.address) - await mine(2, { interval: executionDelay - 1 }) + await mine(2, { interval: executionDelay - 2 }) await mockAMB.__callQueueOnExecutor(secondPayload.address) const { result: negativeResult } = await governanceExecutorW3F.run('onRun') expect(negativeResult.canExec).to.equal(false) - await mine(2, { interval: 1 }) + await mine(2, { interval: 2 }) const { result: positiveResult } = await governanceExecutorW3F.run('onRun') @@ -178,12 +178,12 @@ describe('GovernanceExecutor', function () { await mockAMB.__callQueueOnExecutor(firstPayload.address) await mockAMB.__callQueueOnExecutor(secondPayload.address) - await mine(2, { interval: executionDelay - 1 }) + await mine(2, { interval: executionDelay - 2 }) const { result: negativeResult } = await governanceExecutorW3F.run('onRun') expect(negativeResult.canExec).to.equal(false) - await mine(2, { interval: 1 }) + await mine(2, { interval: 2 }) const { result: positiveResult } = await governanceExecutorW3F.run('onRun')