# UpdateSystemContract is not copying gasPriceByChainId state variable to the new upgraded which will halt ZRC20 token withdraw until system contract is updated accordingly

UpdateSystemContract omits copying gasPriceByChainId to the new system contract, making ZRC20 withdraw revert with ZeroGasPrice until observers re-vote gas prices.

- Payout: Contest reward
- Severity: medium
- Program: ZetaChain
- Credited: dontonka
- Disclosed: 2024-09-06
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-11-zetachain-findings/issues/235
- Library page: https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AM-28
- Tags: domain:web3 impact:dos kind:contest platform:code4rena sev:medium tech:evm technique:source-review vuln:accounting-error vuln:business-logic vuln:misconfiguration

**Target:** Cosmos SDK fungible module msg_server UpdateSystemContract / SystemContract.sol
**Root cause:** The upgrade loop copies gasCoinZRC20ByChainId and gasZetaPoolByChainId via setGasCoinZRC20/setGasZetaPool but never calls setGasPrice, so gasPriceByChainId on the new contract stays 0.
**Steps:**
1. Deploy old system contract and gas ZRC20s for two chains; call `k.SetGasPrice(ctx, chainID, 41)` and `52`.
2. Deploy a new SystemContract and call `msgServer.UpdateSystemContract(ctx, NewMsgUpdateSystemContract(admin, newSystemContract.Hex()))`.
3. Query `gasPriceByChainId(chainID)` on the new contract via CallEVM — returns 0 instead of 41.
4. Call `ZRC20.withdraw`; `withdrawGasFee` reads gasPriceByChainId == 0 and reverts `ZeroGasPrice()`.
**How found:** Code review of the UpdateSystemContract loop noticed setGasCoinZRC20 and setGasZetaPool were called but setGasPrice was absent; confirmed by adding a unit test asserting gasPriceByChainId equals 41/52 on the new contract, which failed with actual 0.
**Exploitation:** Not attacker-triggered; requires admin Group 2 to execute UpdateSystemContract. Post-upgrade, all ZRC20 withdraw calls revert until observers re-run GasPriceVoter on each supported chain.
**Insight:** When an upgrade/migration routine copies a subset of per-chain config to a new contract, enumerate every state variable the old contract exposes and check each is re-initialized; missing gas/oracle params silently zero out and break downstream withdraw paths.
