Skip to content

Commit c65d44a

Browse files
feat(batcher): add workflow to change gas limit
1 parent 8eaec28 commit c65d44a

File tree

3 files changed

+344
-0
lines changed

3 files changed

+344
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
name: Change Batcher Contract Transfer Gas Limit
2+
on:
3+
workflow_dispatch:
4+
inputs:
5+
chain:
6+
description: 'Chain network (e.g., tbsc, bsc, eth, etc.)'
7+
required: true
8+
type: string
9+
batcherContractAddress:
10+
description: 'Batcher contract address'
11+
required: true
12+
type: string
13+
newGasLimit:
14+
description: 'New gas limit for transfers'
15+
required: true
16+
type: string
17+
18+
jobs:
19+
lint-and-test:
20+
environment: dev
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v4
24+
- name: Use Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
cache: 'npm'
28+
- run: npm install
29+
- run: npm run lint
30+
- run: npm run test
31+
env:
32+
MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }}
33+
TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }}
34+
PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT }}
35+
PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP }}
36+
PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT }}
37+
PRIVATE_KEY_FOR_BATCHER_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_BATCHER_CONTRACT_DEPLOYMENT }}
38+
QUICKNODE_ETH_MAINNET_API_KEY: ${{ secrets.QUICKNODE_ETH_MAINNET_API_KEY }}
39+
QUICKNODE_ETH_HOLESKY_API_KEY: ${{ secrets.QUICKNODE_ETH_HOLESKY_API_KEY }}
40+
QUICKNODE_ARBITRUM_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_ARBITRUM_SEPOLIA_API_KEY }}
41+
QUICKNODE_ARBITRUM_ONE_API_KEY: ${{ secrets.QUICKNODE_ARBITRUM_ONE_API_KEY }}
42+
QUICKNODE_OPTIMISM_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_OPTIMISM_SEPOLIA_API_KEY }}
43+
QUICKNODE_OPTIMISM_API_KEY: ${{ secrets.QUICKNODE_OPTIMISM_API_KEY }}
44+
QUICKNODE_ZKSYNC_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_ZKSYNC_SEPOLIA_API_KEY }}
45+
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
46+
ALCHEMY_POLYGON_API_KEY: ${{ secrets.ALCHEMY_POLYGON_API_KEY }}
47+
POLYGONSCAN_API_KEY: ${{ secrets.POLYGONSCAN_API_KEY }}
48+
BSCSCAN_API_KEY: ${{ secrets.BSCSCAN_API_KEY }}
49+
ARBISCAN_API_KEY: ${{ secrets.ARBISCAN_API_KEY }}
50+
OPTIMISTIC_ETHERSCAN_API_KEY: ${{ secrets.OPTIMISTIC_ETHERSCAN_API_KEY }}
51+
ZKSYNC_EXPLORER_API_KEY: ${{ secrets.ZKSYNC_EXPLORER_API_KEY }}
52+
BASESCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY }}
53+
CARTIO_BERA_EXPLORER_API_KEY: ${{ secrets.CARTIO_BERA_EXPLORER_API_KEY }}
54+
BERA_EXPLORER_API_KEY: ${{ secrets.BERA_EXPLORER_API_KEY }}
55+
OAS_EXPLORER_API_KEY: ${{ secrets.OAS_EXPLORER_API_KEY }}
56+
CORE_DAO_TESTNET_EXPLORER_API_KEY: ${{ secrets.CORE_DAO_TESTNET_EXPLORER_API_KEY }}
57+
CORE_DAO_MAINNET_EXPLORER_API_KEY: ${{ secrets.CORE_DAO_MAINNET_EXPLORER_API_KEY }}
58+
FLARE_EXPLORER_API_KEY: ${{ secrets.FLARE_EXPLORER_API_KEY }}
59+
SONGBIRD_EXPLORER_API_KEY: ${{ secrets.SONGBIRD_EXPLORER_API_KEY }}
60+
XDC_EXPLORER_API_KEY: ${{ secrets.XDC_EXPLORER_API_KEY }}
61+
WEMIX_EXPLORER_API_KEY: ${{ secrets.WEMIX_EXPLORER_API_KEY }}
62+
BERA_RPC_URL: ${{ secrets.BERA_RPC_URL }}
63+
MONAD_EXPLORER_API_KEY: ${{ secrets.MONAD_EXPLORER_API_KEY }}
64+
SOMNIA_EXPLORER_API_KEY: ${{ secrets.SOMNIA_EXPLORER_API_KEY }}
65+
SONEIUM_EXPLORER_API_KEY: ${{ secrets.SONEIUM_EXPLORER_API_KEY }}
66+
WORLD_EXPLORER_API_KEY: ${{ secrets.WORLD_EXPLORER_API_KEY }}
67+
CTC_EXPLORER_API_KEY: ${{ secrets.CTC_EXPLORER_API_KEY }}
68+
PHAROS_EXPLORER_API_KEY: ${{ secrets.PHAROS_EXPLORER_API_KEY }}
69+
HYPEEVM_EXPLORER_API_KEY: ${{ secrets.HYPEEVM_EXPLORER_API_KEY }}
70+
SEIEVM_EXPLORER_API_KEY: ${{ secrets.SEIEVM_EXPLORER_API_KEY }}
71+
KAIA_EXPLORER_API_KEY: ${{ secrets.KAIA_EXPLORER_API_KEY }}
72+
IP_EXPLORER_API_KEY: ${{ secrets.IP_EXPLORER_API_KEY }}
73+
74+
change-batcher-gas-limit:
75+
runs-on: ubuntu-latest
76+
needs: [lint-and-test]
77+
environment: ${{ contains(github.event.inputs.chain, 't') && 'testnet' || 'mainnet' }}
78+
steps:
79+
- uses: actions/checkout@v4
80+
- name: Use Node.js
81+
uses: actions/setup-node@v4
82+
with:
83+
node-version: 18.x
84+
cache: 'npm'
85+
- run: npm install
86+
- run: npm run change-batcher-gas-limit --network ${{ github.event.inputs.chain }}
87+
env:
88+
BATCHER_CONTRACT_ADDRESS: ${{ github.event.inputs.batcherContractAddress }}
89+
NEW_GAS_LIMIT: ${{ github.event.inputs.newGasLimit }}
90+
MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }}
91+
TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }}
92+
PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT }}
93+
PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP }}
94+
PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT }}
95+
PRIVATE_KEY_FOR_BATCHER_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_BATCHER_CONTRACT_DEPLOYMENT }}
96+
QUICKNODE_ETH_MAINNET_API_KEY: ${{ secrets.QUICKNODE_ETH_MAINNET_API_KEY }}
97+
QUICKNODE_ETH_HOLESKY_API_KEY: ${{ secrets.QUICKNODE_ETH_HOLESKY_API_KEY }}
98+
QUICKNODE_ARBITRUM_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_ARBITRUM_SEPOLIA_API_KEY }}
99+
QUICKNODE_ARBITRUM_ONE_API_KEY: ${{ secrets.QUICKNODE_ARBITRUM_ONE_API_KEY }}
100+
QUICKNODE_OPTIMISM_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_OPTIMISM_SEPOLIA_API_KEY }}
101+
QUICKNODE_OPTIMISM_API_KEY: ${{ secrets.QUICKNODE_OPTIMISM_API_KEY }}
102+
QUICKNODE_ZKSYNC_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_ZKSYNC_SEPOLIA_API_KEY }}
103+
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
104+
ALCHEMY_POLYGON_API_KEY: ${{ secrets.ALCHEMY_POLYGON_API_KEY }}
105+
POLYGONSCAN_API_KEY: ${{ secrets.POLYGONSCAN_API_KEY }}
106+
BSCSCAN_API_KEY: ${{ secrets.BSCSCAN_API_KEY }}
107+
ARBISCAN_API_KEY: ${{ secrets.ARBISCAN_API_KEY }}
108+
OPTIMISTIC_ETHERSCAN_API_KEY: ${{ secrets.OPTIMISTIC_ETHERSCAN_API_KEY }}
109+
ZKSYNC_EXPLORER_API_KEY: ${{ secrets.ZKSYNC_EXPLORER_API_KEY }}
110+
BASESCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY }}
111+
CARTIO_BERA_EXPLORER_API_KEY: ${{ secrets.CARTIO_BERA_EXPLORER_API_KEY }}
112+
BERA_EXPLORER_API_KEY: ${{ secrets.BERA_EXPLORER_API_KEY }}
113+
OAS_EXPLORER_API_KEY: ${{ secrets.OAS_EXPLORER_API_KEY }}
114+
CORE_DAO_TESTNET_EXPLORER_API_KEY: ${{ secrets.CORE_DAO_TESTNET_EXPLORER_API_KEY }}
115+
CORE_DAO_MAINNET_EXPLORER_API_KEY: ${{ secrets.CORE_DAO_MAINNET_EXPLORER_API_KEY }}
116+
FLARE_EXPLORER_API_KEY: ${{ secrets.FLARE_EXPLORER_API_KEY }}
117+
SONGBIRD_EXPLORER_API_KEY: ${{ secrets.SONGBIRD_EXPLORER_API_KEY }}
118+
XDC_EXPLORER_API_KEY: ${{ secrets.XDC_EXPLORER_API_KEY }}
119+
WEMIX_EXPLORER_API_KEY: ${{ secrets.WEMIX_EXPLORER_API_KEY }}
120+
BERA_RPC_URL: ${{ secrets.BERA_RPC_URL }}
121+
MONAD_EXPLORER_API_KEY: ${{ secrets.MONAD_EXPLORER_API_KEY }}
122+
SOMNIA_EXPLORER_API_KEY: ${{ secrets.SOMNIA_EXPLORER_API_KEY }}
123+
SONEIUM_EXPLORER_API_KEY: ${{ secrets.SONEIUM_EXPLORER_API_KEY }}
124+
WORLD_EXPLORER_API_KEY: ${{ secrets.WORLD_EXPLORER_API_KEY }}
125+
CTC_EXPLORER_API_KEY: ${{ secrets.CTC_EXPLORER_API_KEY }}
126+
PHAROS_EXPLORER_API_KEY: ${{ secrets.PHAROS_EXPLORER_API_KEY }}
127+
HYPEEVM_EXPLORER_API_KEY: ${{ secrets.HYPEEVM_EXPLORER_API_KEY }}
128+
SEIEVM_EXPLORER_API_KEY: ${{ secrets.SEIEVM_EXPLORER_API_KEY }}
129+
KAIA_EXPLORER_API_KEY: ${{ secrets.KAIA_EXPLORER_API_KEY }}
130+
IP_EXPLORER_API_KEY: ${{ secrets.IP_EXPLORER_API_KEY }}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"deploy-prod": "hardhat run scripts/deploy.ts --network",
1313
"deploy-test": "hardhat run scripts/deploy.ts --network",
1414
"deploy-batcher": "hardhat run scripts/deployBatcherContract.ts --network",
15+
"change-batcher-gas-limit": "hardhat run scripts/changeBatcherGasLimit.ts --network",
1516
"test": "hardhat test",
1617
"coverage": "hardhat coverage",
1718
"solhint": "./node_modules/.bin/solhint --fix 'contracts/**/*.sol'",

scripts/changeBatcherGasLimit.ts

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import hre, { ethers } from 'hardhat';
2+
import { logger } from '../deployUtils';
3+
4+
type TxOverrides = {
5+
gasLimit?: number;
6+
maxFeePerGas?: bigint;
7+
maxPriorityFeePerGas?: bigint;
8+
gasPrice?: bigint;
9+
};
10+
11+
async function main() {
12+
logger.step('🔧 Starting Batcher Contract Gas Limit Change 🔧');
13+
14+
const batcherContractAddress = process.env.BATCHER_CONTRACT_ADDRESS;
15+
const newGasLimitStr = process.env.NEW_GAS_LIMIT;
16+
17+
if (!batcherContractAddress) {
18+
throw new Error('BATCHER_CONTRACT_ADDRESS environment variable is required');
19+
}
20+
21+
if (!newGasLimitStr) {
22+
throw new Error('NEW_GAS_LIMIT environment variable is required');
23+
}
24+
25+
const newGasLimit = BigInt(newGasLimitStr);
26+
27+
logger.step('1. Setting up signer and network information...');
28+
29+
const signers = await ethers.getSigners();
30+
if (signers.length < 1) {
31+
throw new Error('No signers available');
32+
}
33+
34+
const desiredIndex = process.env.BATCHER_DEPLOYER_INDEX
35+
? Number(process.env.BATCHER_DEPLOYER_INDEX)
36+
: 2;
37+
let batcherOwner = signers[desiredIndex] || signers[signers.length - 1];
38+
39+
const signerInfos = await Promise.all(
40+
signers.map(async (s, i) => {
41+
const addr = await s.getAddress();
42+
const bal = await ethers.provider.getBalance(addr);
43+
return { index: i, addr, balance: bal };
44+
})
45+
);
46+
47+
if (
48+
!batcherOwner ||
49+
(await ethers.provider.getBalance(await batcherOwner.getAddress())) === 0n
50+
) {
51+
const funded = signerInfos
52+
.filter((x) => x.balance > 0n)
53+
.sort((a, b) => (b.balance > a.balance ? 1 : -1))[0];
54+
if (!funded) {
55+
logger.error('No funded signer available for transaction. Aborting.');
56+
throw new Error('No funded signer');
57+
}
58+
batcherOwner = signers[funded.index];
59+
logger.warn(
60+
`Selected alternative funded signer index=${funded.index} address=${
61+
funded.addr
62+
} (balance=${ethers.formatEther(funded.balance)})`
63+
);
64+
}
65+
66+
const address = await batcherOwner.getAddress();
67+
const { chainId } = await ethers.provider.getNetwork();
68+
69+
logger.info(`Network: ${hre.network.name} (Chain ID: ${chainId})`);
70+
logger.info(`Owner Address: ${address}`);
71+
logger.info(`Batcher Contract Address: ${batcherContractAddress}`);
72+
logger.info(`New Gas Limit: ${newGasLimit.toString()}`);
73+
74+
logger.step('2. Connecting to Batcher contract...');
75+
76+
const batcherAbi = [
77+
'function changeTransferGasLimit(uint256 newTransferGasLimit) external',
78+
'function transferGasLimit() external view returns (uint256)',
79+
'function owner() external view returns (address)'
80+
];
81+
82+
const batcherContract = new ethers.Contract(
83+
batcherContractAddress,
84+
batcherAbi,
85+
batcherOwner
86+
);
87+
88+
logger.step('3. Verifying contract ownership...');
89+
90+
try {
91+
const currentOwner = await batcherContract.owner();
92+
logger.info(`Contract Owner: ${currentOwner}`);
93+
94+
if (currentOwner.toLowerCase() !== address.toLowerCase()) {
95+
throw new Error(`Signer ${address} is not the owner of the contract. Owner is ${currentOwner}`);
96+
}
97+
98+
const currentGasLimit = await batcherContract.transferGasLimit();
99+
logger.info(`Current Gas Limit: ${currentGasLimit.toString()}`);
100+
} catch (error) {
101+
logger.error('Failed to verify contract details:', error);
102+
throw error;
103+
}
104+
105+
logger.step('4. Configuring gas parameters for the transaction...');
106+
107+
const eip1559Chains = [10143, 480, 4801, 1946, 1868, 1114, 1112, 1111, 50312];
108+
const feeData = await ethers.provider.getFeeData();
109+
let gasOverrides: TxOverrides | undefined = undefined;
110+
111+
if (Number(chainId) === 50312 || Number(chainId) === 5031) {
112+
const latestBlock = await ethers.provider.getBlock('latest');
113+
const has1559 =
114+
feeData.maxFeePerGas != null && feeData.maxPriorityFeePerGas != null;
115+
116+
if (has1559) {
117+
const minPriority = 1_000_000_000n; // 1 gwei
118+
const priority =
119+
(feeData.maxPriorityFeePerGas ?? 0n) > 0n
120+
? (feeData.maxPriorityFeePerGas as bigint)
121+
: minPriority;
122+
const base = feeData.maxFeePerGas ?? 0n;
123+
const maxFee = base > priority * 2n ? base : priority * 2n;
124+
gasOverrides = { maxFeePerGas: maxFee, maxPriorityFeePerGas: priority };
125+
logger.info(
126+
`EIP-1559 params (Somnia): maxFeePerGas=${String(
127+
maxFee
128+
)}, maxPriorityFeePerGas=${String(priority)}`
129+
);
130+
} else {
131+
const fallbackGasPrice = feeData.gasPrice ?? 6_000_000_000n; // 6 gwei fallback
132+
gasOverrides = { gasPrice: fallbackGasPrice };
133+
logger.info(
134+
`Legacy gasPrice (Somnia): gasPrice=${String(fallbackGasPrice)}`
135+
);
136+
}
137+
138+
const est = await batcherContract.changeTransferGasLimit.estimateGas(
139+
newGasLimit,
140+
gasOverrides || {}
141+
);
142+
143+
const estWithBuffer = (est * 120n) / 100n; // +20%
144+
let chosenGasLimit = Number(estWithBuffer);
145+
if (latestBlock?.gasLimit) {
146+
const blockLimit = Number(latestBlock.gasLimit);
147+
chosenGasLimit = Math.min(chosenGasLimit, Math.floor(blockLimit * 0.95));
148+
}
149+
gasOverrides.gasLimit = chosenGasLimit;
150+
logger.info(
151+
`Estimated gas: ${est.toString()}, using gasLimit=${chosenGasLimit}`
152+
);
153+
} else if (eip1559Chains.includes(Number(chainId))) {
154+
gasOverrides = {
155+
maxFeePerGas: feeData.maxFeePerGas ?? feeData.gasPrice ?? undefined,
156+
maxPriorityFeePerGas:
157+
feeData.maxPriorityFeePerGas ?? feeData.gasPrice ?? undefined
158+
};
159+
logger.info(
160+
`Gas params set: maxFeePerGas=${String(
161+
gasOverrides.maxFeePerGas ?? ''
162+
)}, maxPriorityFeePerGas=${String(
163+
gasOverrides.maxPriorityFeePerGas ?? ''
164+
)}, gasLimit=<auto>`
165+
);
166+
} else {
167+
logger.info(`Using default gas parameters for this network.`);
168+
}
169+
170+
logger.step('5. Calling changeTransferGasLimit function...');
171+
172+
try {
173+
let tx;
174+
if (gasOverrides) {
175+
tx = await batcherContract.changeTransferGasLimit(newGasLimit, gasOverrides);
176+
} else {
177+
tx = await batcherContract.changeTransferGasLimit(newGasLimit);
178+
}
179+
180+
logger.info(`Transaction submitted: ${tx.hash}`);
181+
logger.info('Waiting for transaction confirmation...');
182+
183+
const receipt = await tx.wait();
184+
185+
if (receipt.status === 1) {
186+
logger.success('✅ Transaction confirmed successfully!');
187+
logger.info(` - Block Number: ${receipt.blockNumber}`);
188+
logger.info(` - Gas Used: ${receipt.gasUsed.toString()}`);
189+
} else {
190+
throw new Error('Transaction failed');
191+
}
192+
193+
logger.step('6. Verifying the gas limit change...');
194+
195+
const updatedGasLimit = await batcherContract.transferGasLimit();
196+
logger.info(`Updated Gas Limit: ${updatedGasLimit.toString()}`);
197+
198+
if (updatedGasLimit.toString() === newGasLimit.toString()) {
199+
logger.success('🎉 Gas limit successfully updated! 🎉');
200+
} else {
201+
logger.error(`Gas limit verification failed. Expected: ${newGasLimit.toString()}, Actual: ${updatedGasLimit.toString()}`);
202+
}
203+
204+
} catch (error) {
205+
logger.error('Failed to change gas limit:', error);
206+
throw error;
207+
}
208+
}
209+
210+
main().catch((error) => {
211+
console.error(error);
212+
process.exitCode = 1;
213+
});

0 commit comments

Comments
 (0)