@@ -7,6 +7,9 @@ import "wormhole-solidity-sdk/libraries/BytesParsing.sol";
7
7
contract Governance {
8
8
using BytesParsing for bytes ;
9
9
10
+ // Only 2 Guardian signatures are required for quorum to call the pause function on governed contracts.
11
+ uint PAUSER_QUORUM = 2 ;
12
+
10
13
// "GeneralPurposeGovernance" (left padded)
11
14
bytes32 public constant MODULE =
12
15
0x000000000000000047656E6572616C507572706F7365476F7665726E616E6365 ;
@@ -79,6 +82,8 @@ contract Governance {
79
82
}
80
83
81
84
function performGovernance (bytes calldata vaa ) external {
85
+
86
+
82
87
IWormhole.VM memory verified = _verifyGovernanceVAA (vaa);
83
88
GeneralPurposeGovernanceMessage memory message =
84
89
parseGeneralPurposeGovernanceMessage (verified.payload);
@@ -115,11 +120,23 @@ contract Governance {
115
120
internal
116
121
returns (IWormhole.VM memory parsedVM )
117
122
{
118
- (IWormhole.VM memory vm , bool valid , string memory reason ) =
119
- wormhole.parseAndVerifyVM (encodedVM);
123
+ IWormhole.VM memory vm = wormhole.parseVM (encodedVM);
124
+ GeneralPurposeGovernanceMessage memory message =
125
+ parseGeneralPurposeGovernanceMessage (vm.payload);
120
126
121
- if (! valid) {
122
- revert (reason);
127
+ bytes memory pauseSig = abi.encodeWithSignature ("pause() " );
128
+ if (keccak256 (message.callData) == keccak256 (pauseSig)) {
129
+ // If we're calling the pause() function, only require 2 Guardian signatures
130
+ (bool valid , string memory reason ) = _verifyVMForPause (vm, true );
131
+ if (! valid) {
132
+ revert (reason);
133
+ }
134
+ } else {
135
+ // If we're calling any other function signature, require the full 13 Guardian signatures
136
+ (bool valid , string memory reason ) = wormhole.verifyVM (vm);
137
+ if (! valid) {
138
+ revert (reason);
139
+ }
123
140
}
124
141
125
142
if (vm.emitterChainId != wormhole.governanceChainId ()) {
@@ -135,6 +152,79 @@ contract Governance {
135
152
return vm;
136
153
}
137
154
155
+ /**
156
+ * @dev COPIED FROM WORMHOLE CORE CONTRACT AND RENAMED TO `verifyVMForPause`
157
+ * `verifyVMInternal` serves to validate an arbitrary vm against a valid Guardian set
158
+ * if checkHash is set then the hash field of the vm is verified against the hash of its contents
159
+ * in the case that the vm is securely parsed and the hash field can be trusted, checkHash can be set to false
160
+ * as the check would be redundant
161
+ */
162
+ function _verifyVMForPause (IWormhole.VM memory vm , bool checkHash ) internal view returns (bool valid , string memory reason ) {
163
+ /// @dev Obtain the current guardianSet for the guardianSetIndex provided
164
+ IWormhole.GuardianSet memory guardianSet = wormhole.getGuardianSet (vm.guardianSetIndex);
165
+
166
+ /**
167
+ * Verify that the hash field in the vm matches with the hash of the contents of the vm if checkHash is set
168
+ * WARNING: This hash check is critical to ensure that the vm.hash provided matches with the hash of the body.
169
+ * Without this check, it would not be safe to call verifyVM on it's own as vm.hash can be a valid signed hash
170
+ * but the body of the vm could be completely different from what was actually signed by the guardians
171
+ */
172
+ if (checkHash){
173
+ bytes memory body = abi.encodePacked (
174
+ vm.timestamp,
175
+ vm.nonce,
176
+ vm.emitterChainId,
177
+ vm.emitterAddress,
178
+ vm.sequence,
179
+ vm.consistencyLevel,
180
+ vm.payload
181
+ );
182
+
183
+ bytes32 vmHash = keccak256 (abi.encodePacked (keccak256 (body)));
184
+
185
+ if (vmHash != vm.hash){
186
+ return (false , "vm.hash doesn't match body " );
187
+ }
188
+ }
189
+
190
+ /**
191
+ * @dev Checks whether the guardianSet has zero keys
192
+ * WARNING: This keys check is critical to ensure the guardianSet has keys present AND to ensure
193
+ * that guardianSet key size doesn't fall to zero and negatively impact quorum assessment. If guardianSet
194
+ * key length is 0 and vm.signatures length is 0, this could compromise the integrity of both vm and
195
+ * signature verification.
196
+ */
197
+ if (guardianSet.keys.length == 0 ){
198
+ return (false , "invalid guardian set " );
199
+ }
200
+
201
+ /// @dev Checks if VM guardian set index matches the current index (unless the current set is expired).
202
+ if (vm.guardianSetIndex != wormhole.getCurrentGuardianSetIndex () && guardianSet.expirationTime < block .timestamp ){
203
+ return (false , "guardian set has expired " );
204
+ }
205
+
206
+ /**
207
+ * @dev We're using a fixed point number transformation with 1 decimal to deal with rounding.
208
+ * WARNING: This quorum check is critical to assessing whether we have enough Guardian signatures to validate a VM
209
+ * if making any changes to this, obtain additional peer review. If guardianSet key length is 0 and
210
+ * vm.signatures length is 0, this could compromise the integrity of both vm and signature verification.
211
+ */
212
+ uint fullQuorum = wormhole.quorum (guardianSet.keys.length );
213
+ uint requiredPauseQuorum = PAUSER_QUORUM < fullQuorum ? PAUSER_QUORUM : fullQuorum;
214
+ if (vm.signatures.length < requiredPauseQuorum){
215
+ return (false , "no quorum " );
216
+ }
217
+
218
+ /// @dev Verify the proposed vm.signatures against the guardianSet
219
+ (bool signaturesValid , string memory invalidReason ) = wormhole.verifySignatures (vm.hash, vm.signatures, guardianSet);
220
+ if (! signaturesValid){
221
+ return (false , invalidReason);
222
+ }
223
+
224
+ /// If we are here, we've validated the VM is a valid multi-sig that matches the guardianSet.
225
+ return (true , "" );
226
+ }
227
+
138
228
function encodeGeneralPurposeGovernanceMessage (GeneralPurposeGovernanceMessage memory m )
139
229
public
140
230
pure
0 commit comments