Skip to content

Commit e39277d

Browse files
authored
[eth] Add setWormholeAddress governance message (#917)
* [eth] Add setWormholeAddress governance message * Address review feedbacks * Add signatures to errors This will help with debugging * Update abi
1 parent e4d494d commit e39277d

File tree

9 files changed

+247
-3
lines changed

9 files changed

+247
-3
lines changed

governance/xc_governance_sdk_js/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export {
22
DataSource,
33
AptosAuthorizeUpgradeContractInstruction,
44
EthereumUpgradeContractInstruction,
5+
EthereumSetWormholeAddress,
56
HexString20Bytes,
67
HexString32Bytes,
78
SetDataSourcesInstruction,

governance/xc_governance_sdk_js/src/instructions.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ enum TargetAction {
1414
SetFee,
1515
SetValidPeriod,
1616
RequestGovernanceDataSourceTransfer,
17+
SetWormholeAddress,
1718
}
1819

1920
abstract class HexString implements Serializable {
@@ -194,3 +195,13 @@ export class RequestGovernanceDataSourceTransferInstruction extends TargetInstru
194195
.build();
195196
}
196197
}
198+
199+
export class EthereumSetWormholeAddress extends TargetInstruction {
200+
constructor(targetChainId: ChainId, private address: HexString20Bytes) {
201+
super(TargetAction.SetWormholeAddress, targetChainId);
202+
}
203+
204+
protected serializePayload(): Buffer {
205+
return this.address.serialize();
206+
}
207+
}

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ abstract contract PythGovernance is
3434
);
3535
event FeeSet(uint oldFee, uint newFee);
3636
event ValidPeriodSet(uint oldValidPeriod, uint newValidPeriod);
37+
event WormholeAddressSet(
38+
address oldWormholeAddress,
39+
address newWormholeAddress
40+
);
3741

3842
function verifyGovernanceVM(
3943
bytes memory encodedVM
@@ -86,6 +90,13 @@ abstract contract PythGovernance is
8690
) {
8791
// RequestGovernanceDataSourceTransfer can be only part of AuthorizeGovernanceDataSourceTransfer message
8892
revert PythErrors.InvalidGovernanceMessage();
93+
} else if (gi.action == GovernanceAction.SetWormholeAddress) {
94+
if (gi.targetChainId == 0)
95+
revert PythErrors.InvalidGovernanceTarget();
96+
setWormholeAddress(
97+
parseSetWormholeAddressPayload(gi.payload),
98+
encodedVM
99+
);
89100
} else {
90101
revert PythErrors.InvalidGovernanceMessage();
91102
}
@@ -190,4 +201,46 @@ abstract contract PythGovernance is
190201

191202
emit ValidPeriodSet(oldValidPeriod, validTimePeriodSeconds());
192203
}
204+
205+
function setWormholeAddress(
206+
SetWormholeAddressPayload memory payload,
207+
bytes memory encodedVM
208+
) internal {
209+
address oldWormholeAddress = address(wormhole());
210+
setWormhole(payload.newWormholeAddress);
211+
212+
// We want to verify that the new wormhole address is valid, so we make sure that it can
213+
// parse and verify the same governance VAA that is used to set it.
214+
(IWormhole.VM memory vm, bool valid, ) = wormhole().parseAndVerifyVM(
215+
encodedVM
216+
);
217+
218+
if (!valid) revert PythErrors.InvalidGovernanceMessage();
219+
220+
if (!isValidGovernanceDataSource(vm.emitterChainId, vm.emitterAddress))
221+
revert PythErrors.InvalidGovernanceMessage();
222+
223+
if (vm.sequence != lastExecutedGovernanceSequence())
224+
revert PythErrors.InvalidWormholeAddressToSet();
225+
226+
GovernanceInstruction memory gi = parseGovernanceInstruction(
227+
vm.payload
228+
);
229+
230+
if (gi.action != GovernanceAction.SetWormholeAddress)
231+
revert PythErrors.InvalidWormholeAddressToSet();
232+
233+
// Purposefully, we don't check whether the chainId is the same as the current chainId because
234+
// we might want to change the chain id of the wormhole contract.
235+
236+
// The following check is not necessary for security, but is a sanity check that the new wormhole
237+
// contract parses the payload correctly.
238+
SetWormholeAddressPayload
239+
memory newPayload = parseSetWormholeAddressPayload(gi.payload);
240+
241+
if (newPayload.newWormholeAddress != payload.newWormholeAddress)
242+
revert PythErrors.InvalidWormholeAddressToSet();
243+
244+
emit WormholeAddressSet(oldWormholeAddress, address(wormhole()));
245+
}
193246
}

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ contract PythGovernanceInstructions {
3030
SetDataSources, // 2
3131
SetFee, // 3
3232
SetValidPeriod, // 4
33-
RequestGovernanceDataSourceTransfer // 5
33+
RequestGovernanceDataSourceTransfer, // 5
34+
SetWormholeAddress // 6
3435
}
3536

3637
struct GovernanceInstruction {
@@ -69,6 +70,10 @@ contract PythGovernanceInstructions {
6970
uint newValidPeriod;
7071
}
7172

73+
struct SetWormholeAddressPayload {
74+
address newWormholeAddress;
75+
}
76+
7277
/// @dev Parse a GovernanceInstruction
7378
function parseGovernanceInstruction(
7479
bytes memory encodedInstruction
@@ -199,4 +204,17 @@ contract PythGovernanceInstructions {
199204
if (encodedPayload.length != index)
200205
revert PythErrors.InvalidGovernanceMessage();
201206
}
207+
208+
/// @dev Parse a UpdateWormholeAddressPayload (action 6) with minimal validation
209+
function parseSetWormholeAddressPayload(
210+
bytes memory encodedPayload
211+
) public pure returns (SetWormholeAddressPayload memory sw) {
212+
uint index = 0;
213+
214+
sw.newWormholeAddress = address(encodedPayload.toAddress(index));
215+
index += 20;
216+
217+
if (encodedPayload.length != index)
218+
revert PythErrors.InvalidGovernanceMessage();
219+
}
202220
}

target_chains/ethereum/contracts/hardhat.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ module.exports = {
8080
settings: {
8181
optimizer: {
8282
enabled: true,
83-
runs: 5000,
83+
runs: 2000,
8484
},
8585
},
8686
},

target_chains/ethereum/contracts/test/pyth.js

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,18 @@ const {
1010
const { assert, expect } = require("chai");
1111

1212
// Use "WormholeReceiver" if you are testing with Wormhole Receiver
13+
const Setup = artifacts.require("Setup");
14+
const Implementation = artifacts.require("Implementation");
1315
const Wormhole = artifacts.require("Wormhole");
1416

17+
const ReceiverSetup = artifacts.require("ReceiverSetup");
18+
const ReceiverImplementation = artifacts.require("ReceiverImplementation");
19+
const WormholeReceiver = artifacts.require("WormholeReceiver");
20+
21+
const wormholeGovernanceChainId = governance.CHAINS.solana;
22+
const wormholeGovernanceContract =
23+
"0x0000000000000000000000000000000000000000000000000000000000000004";
24+
1525
const PythUpgradable = artifacts.require("PythUpgradable");
1626
const MockPythUpgrade = artifacts.require("MockPythUpgrade");
1727
const MockUpgradeableProxy = artifacts.require("MockUpgradeableProxy");
@@ -1069,6 +1079,136 @@ contract("Pyth", function () {
10691079
// and adding it here will cause more complexity (and is not so short).
10701080
});
10711081

1082+
it("Setting wormhole address should work", async function () {
1083+
// Deploy a new wormhole contract
1084+
const newSetup = await Setup.new();
1085+
const newImpl = await Implementation.new();
1086+
1087+
// encode initialisation data
1088+
const initData = newSetup.contract.methods
1089+
.setup(
1090+
newImpl.address,
1091+
[testSigner1.address],
1092+
governance.CHAINS.polygon, // changing the chain id to polygon
1093+
wormholeGovernanceChainId,
1094+
wormholeGovernanceContract
1095+
)
1096+
.encodeABI();
1097+
1098+
const newWormhole = await Wormhole.new(newSetup.address, initData);
1099+
1100+
// Creating the vaa to set the new wormhole address
1101+
const data = new governance.EthereumSetWormholeAddress(
1102+
governance.CHAINS.ethereum,
1103+
new governance.HexString20Bytes(newWormhole.address)
1104+
).serialize();
1105+
1106+
const vaa = await createVAAFromUint8Array(
1107+
data,
1108+
testGovernanceChainId,
1109+
testGovernanceEmitter,
1110+
1
1111+
);
1112+
1113+
assert.equal(await this.pythProxy.chainId(), governance.CHAINS.ethereum);
1114+
1115+
const oldWormholeAddress = await this.pythProxy.wormhole();
1116+
1117+
const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
1118+
expectEvent(receipt, "WormholeAddressSet", {
1119+
oldWormholeAddress: oldWormholeAddress,
1120+
newWormholeAddress: newWormhole.address,
1121+
});
1122+
1123+
assert.equal(await this.pythProxy.wormhole(), newWormhole.address);
1124+
assert.equal(await this.pythProxy.chainId(), governance.CHAINS.polygon);
1125+
});
1126+
1127+
it("Setting wormhole address to WormholeReceiver should work", async function () {
1128+
// Deploy a new wormhole receiver contract
1129+
const newReceiverSetup = await ReceiverSetup.new();
1130+
const newReceiverImpl = await ReceiverImplementation.new();
1131+
1132+
// encode initialisation data
1133+
const initData = newReceiverSetup.contract.methods
1134+
.setup(
1135+
newReceiverImpl.address,
1136+
[testSigner1.address],
1137+
governance.CHAINS.polygon, // changing the chain id to polygon
1138+
wormholeGovernanceChainId,
1139+
wormholeGovernanceContract
1140+
)
1141+
.encodeABI();
1142+
1143+
const newWormholeReceiver = await WormholeReceiver.new(
1144+
newReceiverSetup.address,
1145+
initData
1146+
);
1147+
1148+
// Creating the vaa to set the new wormhole address
1149+
const data = new governance.EthereumSetWormholeAddress(
1150+
governance.CHAINS.ethereum,
1151+
new governance.HexString20Bytes(newWormholeReceiver.address)
1152+
).serialize();
1153+
1154+
const vaa = await createVAAFromUint8Array(
1155+
data,
1156+
testGovernanceChainId,
1157+
testGovernanceEmitter,
1158+
1
1159+
);
1160+
1161+
assert.equal(await this.pythProxy.chainId(), governance.CHAINS.ethereum);
1162+
1163+
const oldWormholeAddress = await this.pythProxy.wormhole();
1164+
1165+
const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
1166+
expectEvent(receipt, "WormholeAddressSet", {
1167+
oldWormholeAddress: oldWormholeAddress,
1168+
newWormholeAddress: newWormholeReceiver.address,
1169+
});
1170+
1171+
assert.equal(await this.pythProxy.wormhole(), newWormholeReceiver.address);
1172+
assert.equal(await this.pythProxy.chainId(), governance.CHAINS.polygon);
1173+
});
1174+
1175+
it("Setting wormhole address to a wrong contract should reject", async function () {
1176+
// Deploy a new wormhole contract
1177+
const newSetup = await Setup.new();
1178+
const newImpl = await Implementation.new();
1179+
1180+
// encode initialisation data
1181+
const initData = newSetup.contract.methods
1182+
.setup(
1183+
newImpl.address,
1184+
[testSigner2.address], // A wrong signer
1185+
governance.CHAINS.ethereum,
1186+
wormholeGovernanceChainId,
1187+
wormholeGovernanceContract
1188+
)
1189+
.encodeABI();
1190+
1191+
const newWormhole = await Wormhole.new(newSetup.address, initData);
1192+
1193+
// Creating the vaa to set the new wormhole address
1194+
const data = new governance.EthereumSetWormholeAddress(
1195+
governance.CHAINS.ethereum,
1196+
new governance.HexString20Bytes(newWormhole.address)
1197+
).serialize();
1198+
1199+
const wrongVaa = await createVAAFromUint8Array(
1200+
data,
1201+
testGovernanceChainId,
1202+
testGovernanceEmitter,
1203+
1
1204+
);
1205+
1206+
await expectRevertCustomError(
1207+
this.pythProxy.executeGovernanceInstruction(wrongVaa),
1208+
"InvalidGovernanceMessage"
1209+
);
1210+
});
1211+
10721212
// Version
10731213

10741214
it("Make sure version is the npm package version", async function () {

target_chains/ethereum/contracts/truffle-config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ module.exports = {
288288
settings: {
289289
optimizer: {
290290
enabled: true,
291-
runs: 5000,
291+
runs: 2000,
292292
},
293293
},
294294
},

target_chains/ethereum/sdk/solidity/PythErrors.sol

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,45 @@ pragma solidity ^0.8.0;
44

55
library PythErrors {
66
// Function arguments are invalid (e.g., the arguments lengths mismatch)
7+
// Signature: 0xa9cb9e0d
78
error InvalidArgument();
89
// Update data is coming from an invalid data source.
10+
// Signature: 0xe60dce71
911
error InvalidUpdateDataSource();
1012
// Update data is invalid (e.g., deserialization error)
13+
// Signature: 0xe69ffece
1114
error InvalidUpdateData();
1215
// Insufficient fee is paid to the method.
16+
// Signature: 0x025dbdd4
1317
error InsufficientFee();
1418
// There is no fresh update, whereas expected fresh updates.
19+
// Signature: 0xde2c57fa
1520
error NoFreshUpdate();
1621
// There is no price feed found within the given range or it does not exists.
22+
// Signature: 0x45805f5d
1723
error PriceFeedNotFoundWithinRange();
1824
// Price feed not found or it is not pushed on-chain yet.
25+
// Signature: 0x14aebe68
1926
error PriceFeedNotFound();
2027
// Requested price is stale.
28+
// Signature: 0x19abf40e
2129
error StalePrice();
2230
// Given message is not a valid Wormhole VAA.
31+
// Signature: 0x2acbe915
2332
error InvalidWormholeVaa();
2433
// Governance message is invalid (e.g., deserialization error).
34+
// Signature: 0x97363b35
2535
error InvalidGovernanceMessage();
2636
// Governance message is not for this contract.
37+
// Signature: 0x63daeb77
2738
error InvalidGovernanceTarget();
2839
// Governance message is coming from an invalid data source.
40+
// Signature: 0x360f2d87
2941
error InvalidGovernanceDataSource();
3042
// Governance message is old.
43+
// Signature: 0x88d1b847
3144
error OldGovernanceMessage();
45+
// The wormhole address to set in SetWormholeAddress governance is invalid.
46+
// Signature: 0x13d3ed82
47+
error InvalidWormholeAddressToSet();
3248
}

target_chains/ethereum/sdk/solidity/abis/PythErrors.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
"name": "InvalidUpdateDataSource",
3535
"type": "error"
3636
},
37+
{
38+
"inputs": [],
39+
"name": "InvalidWormholeAddressToSet",
40+
"type": "error"
41+
},
3742
{
3843
"inputs": [],
3944
"name": "InvalidWormholeVaa",

0 commit comments

Comments
 (0)