Skip to content

Commit 2b4a6b7

Browse files
committed
Add verifier to pyth evm contract
1 parent 3471777 commit 2b4a6b7

File tree

12 files changed

+586
-472
lines changed

12 files changed

+586
-472
lines changed

doc/code-guidelines.md

Lines changed: 173 additions & 173 deletions
Large diffs are not rendered by default.

doc/rust-code-guidelines.md

Lines changed: 284 additions & 286 deletions
Large diffs are not rendered by default.

target_chains/ethereum/contracts/contracts/pyth/Pyth.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ abstract contract Pyth is
2525
bytes32 governanceEmitterAddress,
2626
uint64 governanceInitialSequence,
2727
uint validTimePeriodSeconds,
28-
uint singleUpdateFeeInWei
28+
uint singleUpdateFeeInWei,
29+
address verifier
2930
) internal {
3031
setWormhole(wormhole);
32+
setVerifier(verifier);
3133

3234
if (
3335
dataSourceEmitterChainIds.length !=

target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,15 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
3737
{
3838
bool valid;
3939
(vm, valid, ) = wormhole().parseAndVerifyVM(encodedVm);
40-
if (!valid) revert PythErrors.InvalidWormholeVaa();
40+
if (!valid) {
41+
if (address(verifier()) == address(0)) {
42+
revert PythErrors.InvalidUpdateData();
43+
}
44+
(vm, valid, ) = verifier().parseAndVerifyVM(encodedVm);
45+
if (!valid) {
46+
revert PythErrors.InvalidUpdateData();
47+
}
48+
}
4149
}
4250

4351
if (!isValidDataSource(vm.emitterChainId, vm.emitterAddress))

target_chains/ethereum/contracts/contracts/pyth/PythGetters.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,8 @@ contract PythGetters is PythState {
9595
function transactionFeeInWei() public view returns (uint) {
9696
return _state.transactionFeeInWei;
9797
}
98+
99+
function verifier() public view returns (IWormhole) {
100+
return IWormhole(_state.verifier);
101+
}
98102
}

target_chains/ethereum/contracts/contracts/pyth/PythGovernance.sol

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,33 @@ abstract contract PythGovernance is
4040
);
4141
event TransactionFeeSet(uint oldFee, uint newFee);
4242
event FeeWithdrawn(address targetAddress, uint fee);
43+
event VerifierAddressSet(
44+
address oldVerifierAddress,
45+
address newVerifierAddress
46+
);
4347

44-
function verifyGovernanceVM(
48+
// Check whether the encodedVM is signed by either the Wormhole or the Verifier.
49+
function validateVm(
4550
bytes memory encodedVM
46-
) internal returns (IWormhole.VM memory parsedVM) {
51+
) internal view returns (IWormhole.VM memory parsedVM) {
4752
(IWormhole.VM memory vm, bool valid, ) = wormhole().parseAndVerifyVM(
4853
encodedVM
4954
);
55+
if (valid) return vm;
5056

51-
if (!valid) revert PythErrors.InvalidWormholeVaa();
57+
if (address(verifier()) != address(0)) {
58+
(IWormhole.VM memory vmv, bool validv, ) = verifier()
59+
.parseAndVerifyVM(encodedVM);
60+
if (validv) return vmv;
61+
}
62+
63+
revert PythErrors.InvalidWormholeVaa();
64+
}
65+
66+
function verifyGovernanceVM(
67+
bytes memory encodedVM
68+
) internal returns (IWormhole.VM memory parsedVM) {
69+
IWormhole.VM memory vm = validateVm(encodedVM);
5270

5371
if (!isValidGovernanceDataSource(vm.emitterChainId, vm.emitterAddress))
5472
revert PythErrors.InvalidGovernanceDataSource();
@@ -105,6 +123,13 @@ abstract contract PythGovernance is
105123
setTransactionFee(parseSetTransactionFeePayload(gi.payload));
106124
} else if (gi.action == GovernanceAction.WithdrawFee) {
107125
withdrawFee(parseWithdrawFeePayload(gi.payload));
126+
} else if (gi.action == GovernanceAction.SetVerifierAddress) {
127+
if (gi.targetChainId == 0)
128+
revert PythErrors.InvalidGovernanceTarget();
129+
setVerifierAddress(
130+
parseSetVerifierAddressPayload(gi.payload),
131+
encodedVM
132+
);
108133
} else {
109134
revert PythErrors.InvalidGovernanceMessage();
110135
}
@@ -131,11 +156,8 @@ abstract contract PythGovernance is
131156
// Make sure the claimVaa is a valid VAA with RequestGovernanceDataSourceTransfer governance message
132157
// If it's valid then its emitter can take over the governance from the current emitter.
133158
// The VAA is checked here to ensure that the new governance data source is valid and can send message
134-
// through wormhole.
135-
(IWormhole.VM memory vm, bool valid, ) = wormhole().parseAndVerifyVM(
136-
payload.claimVaa
137-
);
138-
if (!valid) revert PythErrors.InvalidWormholeVaa();
159+
// through wormhole or verifier.
160+
IWormhole.VM memory vm = validateVm(payload.claimVaa);
139161

140162
GovernanceInstruction memory gi = parseGovernanceInstruction(
141163
vm.payload
@@ -210,6 +232,8 @@ abstract contract PythGovernance is
210232
emit ValidPeriodSet(oldValidPeriod, validTimePeriodSeconds());
211233
}
212234

235+
// If the VAA was created by Verifier, this will revert,
236+
// because it assumes that the new Wormhole is able to parse and verify the governance VAA.
213237
function setWormholeAddress(
214238
SetWormholeAddressPayload memory payload,
215239
bytes memory encodedVM
@@ -270,4 +294,48 @@ abstract contract PythGovernance is
270294

271295
emit FeeWithdrawn(payload.targetAddress, payload.fee);
272296
}
297+
298+
// If the VAA was created by Wormhole, this will revert,
299+
// because it assumes that the new Verifier is able to parse and verify the governance VAA.
300+
function setVerifierAddress(
301+
SetVerifierAddressPayload memory payload,
302+
bytes memory encodedVM
303+
) internal {
304+
address oldVerifierAddress = address(verifier());
305+
setVerifier(payload.newVerifierAddress);
306+
307+
// We want to verify that the new verifier address is valid, so we make sure that it can
308+
// parse and verify the same governance VAA that is used to set it.
309+
(IWormhole.VM memory vm, bool valid, ) = verifier().parseAndVerifyVM(
310+
encodedVM
311+
);
312+
313+
if (!valid) revert PythErrors.InvalidGovernanceMessage();
314+
315+
if (!isValidGovernanceDataSource(vm.emitterChainId, vm.emitterAddress))
316+
revert PythErrors.InvalidGovernanceMessage();
317+
318+
if (vm.sequence != lastExecutedGovernanceSequence())
319+
revert PythErrors.InvalidVerifierAddressToSet();
320+
321+
GovernanceInstruction memory gi = parseGovernanceInstruction(
322+
vm.payload
323+
);
324+
325+
if (gi.action != GovernanceAction.SetVerifierAddress)
326+
revert PythErrors.InvalidVerifierAddressToSet();
327+
328+
// Purposefully, we don't check whether the chainId is the same as the current chainId because
329+
// we might want to change the chain id of the verifier contract.
330+
331+
// The following check is not necessary for security, but is a sanity check that the new verifier
332+
// contract parses the payload correctly.
333+
SetVerifierAddressPayload
334+
memory newPayload = parseSetVerifierAddressPayload(gi.payload);
335+
336+
if (newPayload.newVerifierAddress != payload.newVerifierAddress)
337+
revert PythErrors.InvalidVerifierAddressToSet();
338+
339+
emit VerifierAddressSet(oldVerifierAddress, address(verifier()));
340+
}
273341
}

target_chains/ethereum/contracts/contracts/pyth/PythGovernanceInstructions.sol

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ contract PythGovernanceInstructions {
3737
SetWormholeAddress, // 6
3838
SetFeeInToken, // 7 - No-op for EVM chains
3939
SetTransactionFee, // 8
40-
WithdrawFee // 9
40+
WithdrawFee, // 9
41+
SetVerifierAddress // 10
4142
}
4243

4344
struct GovernanceInstruction {
@@ -90,6 +91,10 @@ contract PythGovernanceInstructions {
9091
uint256 fee;
9192
}
9293

94+
struct SetVerifierAddressPayload {
95+
address newVerifierAddress;
96+
}
97+
9398
/// @dev Parse a GovernanceInstruction
9499
function parseGovernanceInstruction(
95100
bytes memory encodedInstruction
@@ -272,4 +277,17 @@ contract PythGovernanceInstructions {
272277
if (encodedPayload.length != index)
273278
revert PythErrors.InvalidGovernanceMessage();
274279
}
280+
281+
/// @dev Parse a UpdateVerifierAddressPayload (action 10) with minimal validation
282+
function parseSetVerifierAddressPayload(
283+
bytes memory encodedPayload
284+
) public pure returns (SetVerifierAddressPayload memory sv) {
285+
uint index = 0;
286+
287+
sv.newVerifierAddress = address(encodedPayload.toAddress(index));
288+
index += 20;
289+
290+
if (encodedPayload.length != index)
291+
revert PythErrors.InvalidGovernanceMessage();
292+
}
275293
}

target_chains/ethereum/contracts/contracts/pyth/PythSetters.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,8 @@ contract PythSetters is PythState, IPythEvents {
5252
function setTransactionFeeInWei(uint fee) internal {
5353
_state.transactionFeeInWei = fee;
5454
}
55+
56+
function setVerifier(address vf) internal {
57+
_state.verifier = payable(vf);
58+
}
5559
}

target_chains/ethereum/contracts/contracts/pyth/PythState.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ contract PythStorage {
4040
mapping(bytes32 => PythInternalStructs.PriceInfo) latestPriceInfo;
4141
// Fee charged per transaction, in addition to per-update fees
4242
uint transactionFeeInWei;
43+
// Verifier address for verifying VAA signatures
44+
address verifier;
4345
}
4446
}
4547

target_chains/ethereum/contracts/contracts/pyth/PythUpgradable.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ contract PythUpgradable is
2828
bytes32 governanceEmitterAddress,
2929
uint64 governanceInitialSequence,
3030
uint validTimePeriodSeconds,
31-
uint singleUpdateFeeInWei
31+
uint singleUpdateFeeInWei,
32+
address verifier
3233
) public initializer {
3334
__Ownable_init();
3435
__UUPSUpgradeable_init();
@@ -41,7 +42,8 @@ contract PythUpgradable is
4142
governanceEmitterAddress,
4243
governanceInitialSequence,
4344
validTimePeriodSeconds,
44-
singleUpdateFeeInWei
45+
singleUpdateFeeInWei,
46+
verifier
4547
);
4648

4749
renounceOwnership();

0 commit comments

Comments
 (0)