Skip to content

Commit dc5ba1f

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

File tree

3 files changed

+352
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)