pyth-crosschain/ethereum/contracts/pyth/PythGovernance.sol

203 lines
7.3 KiB
Solidity

// contracts/Governance.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;
import "./PythGovernanceInstructions.sol";
import "./PythInternalStructs.sol";
import "./PythGetters.sol";
import "./PythSetters.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
/**
* @dev `Governance` defines a means to enacting changes to the Pyth contract.
*/
abstract contract PythGovernance is
PythGetters,
PythSetters,
PythGovernanceInstructions
{
event ContractUpgraded(
address oldImplementation,
address newImplementation
);
event GovernanceDataSourceSet(
PythInternalStructs.DataSource oldDataSource,
PythInternalStructs.DataSource newDataSource,
uint64 initialSequence
);
event DataSourcesSet(
PythInternalStructs.DataSource[] oldDataSources,
PythInternalStructs.DataSource[] newDataSources
);
event FeeSet(uint oldFee, uint newFee);
event ValidPeriodSet(uint oldValidPeriod, uint newValidPeriod);
function verifyGovernanceVM(
bytes memory encodedVM
) internal returns (IWormhole.VM memory parsedVM) {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole()
.parseAndVerifyVM(encodedVM);
require(valid, reason);
require(
isValidGovernanceDataSource(vm.emitterChainId, vm.emitterAddress),
"VAA is not coming from the governance data source"
);
require(
vm.sequence > lastExecutedGovernanceSequence(),
"VAA is older than the last executed governance VAA"
);
setLastExecutedGovernanceSequence(vm.sequence);
return vm;
}
function executeGovernanceInstruction(bytes calldata encodedVM) public {
IWormhole.VM memory vm = verifyGovernanceVM(encodedVM);
GovernanceInstruction memory gi = parseGovernanceInstruction(
vm.payload
);
require(
gi.targetChainId == chainId() || gi.targetChainId == 0,
"invalid target chain for this governance instruction"
);
if (gi.action == GovernanceAction.UpgradeContract) {
require(
gi.targetChainId != 0,
"upgrade with chain id 0 is not possible"
);
upgradeContract(parseUpgradeContractPayload(gi.payload));
} else if (
gi.action == GovernanceAction.AuthorizeGovernanceDataSourceTransfer
) {
AuthorizeGovernanceDataSourceTransfer(
parseAuthorizeGovernanceDataSourceTransferPayload(gi.payload)
);
} else if (gi.action == GovernanceAction.SetDataSources) {
setDataSources(parseSetDataSourcesPayload(gi.payload));
} else if (gi.action == GovernanceAction.SetFee) {
setFee(parseSetFeePayload(gi.payload));
} else if (gi.action == GovernanceAction.SetValidPeriod) {
setValidPeriod(parseSetValidPeriodPayload(gi.payload));
} else if (
gi.action == GovernanceAction.RequestGovernanceDataSourceTransfer
) {
revert(
"RequestGovernanceDataSourceTransfer can be only part of AuthorizeGovernanceDataSourceTransfer message"
);
} else {
revert("invalid governance action");
}
}
function upgradeContract(UpgradeContractPayload memory payload) internal {
// This method on this contract does not have enough access to execute this, it should be executed on the
// upgradable contract.
upgradeUpgradableContract(payload);
}
function upgradeUpgradableContract(
UpgradeContractPayload memory payload
) internal virtual;
// Transfer the governance data source to a new value with sanity checks
// to ensure the new governance data source can manage the contract.
function AuthorizeGovernanceDataSourceTransfer(
AuthorizeGovernanceDataSourceTransferPayload memory payload
) internal {
PythInternalStructs.DataSource
memory oldGovernanceDatSource = governanceDataSource();
// Make sure the claimVaa is a valid VAA with RequestGovernanceDataSourceTransfer governance message
// If it's valid then its emitter can take over the governance from the current emitter.
// The VAA is checked here to ensure that the new governance data source is valid and can send message
// through wormhole.
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole()
.parseAndVerifyVM(payload.claimVaa);
require(valid, reason);
GovernanceInstruction memory gi = parseGovernanceInstruction(
vm.payload
);
require(
gi.targetChainId == chainId() || gi.targetChainId == 0,
"invalid target chain for this governance instruction"
);
require(
gi.action == GovernanceAction.RequestGovernanceDataSourceTransfer,
"governance data source change inner vaa is not of claim action type"
);
RequestGovernanceDataSourceTransferPayload
memory claimPayload = parseRequestGovernanceDataSourceTransferPayload(
gi.payload
);
// Governance data source index is used to prevent replay attacks, so a claimVaa cannot be used twice.
require(
governanceDataSourceIndex() <
claimPayload.governanceDataSourceIndex,
"cannot upgrade to an older governance data source"
);
setGovernanceDataSourceIndex(claimPayload.governanceDataSourceIndex);
PythInternalStructs.DataSource
memory newGovernanceDS = PythInternalStructs.DataSource(
vm.emitterChainId,
vm.emitterAddress
);
setGovernanceDataSource(newGovernanceDS);
// Setting the last executed governance to the claimVaa sequence to avoid using older sequences.
setLastExecutedGovernanceSequence(vm.sequence);
emit GovernanceDataSourceSet(
oldGovernanceDatSource,
governanceDataSource(),
lastExecutedGovernanceSequence()
);
}
function setDataSources(SetDataSourcesPayload memory payload) internal {
PythInternalStructs.DataSource[]
memory oldDataSources = validDataSources();
for (uint i = 0; i < oldDataSources.length; i += 1) {
_state.isValidDataSource[hashDataSource(oldDataSources[i])] = false;
}
delete _state.validDataSources;
for (uint i = 0; i < payload.dataSources.length; i++) {
_state.validDataSources.push(payload.dataSources[i]);
_state.isValidDataSource[
hashDataSource(payload.dataSources[i])
] = true;
}
emit DataSourcesSet(oldDataSources, validDataSources());
}
function setFee(SetFeePayload memory payload) internal {
uint oldFee = singleUpdateFeeInWei();
setSingleUpdateFeeInWei(payload.newFee);
emit FeeSet(oldFee, singleUpdateFeeInWei());
}
function setValidPeriod(SetValidPeriodPayload memory payload) internal {
uint oldValidPeriod = validTimePeriodSeconds();
setValidTimePeriodSeconds(payload.newValidPeriod);
emit ValidPeriodSet(oldValidPeriod, validTimePeriodSeconds());
}
}