|
1 | 1 | import { PublicKey, SystemProgram } from "@solana/web3.js";
|
2 |
| -import { PythGovernanceHeader, ExecutePostedVaa } from ".."; |
| 2 | +import { |
| 3 | + PythGovernanceHeader, |
| 4 | + ExecutePostedVaa, |
| 5 | + MODULES, |
| 6 | + MODULE_EXECUTOR, |
| 7 | + TargetAction, |
| 8 | + ExecutorAction, |
| 9 | + ActionName, |
| 10 | + PythGovernanceAction, |
| 11 | + decodeGovernancePayload, |
| 12 | +} from ".."; |
| 13 | +import * as fc from "fast-check"; |
| 14 | +import { |
| 15 | + ChainId, |
| 16 | + ChainName, |
| 17 | + CHAINS, |
| 18 | + toChainId, |
| 19 | + toChainName, |
| 20 | +} from "@certusone/wormhole-sdk"; |
| 21 | +import { Arbitrary, IntArrayConstraints } from "fast-check"; |
| 22 | +import { |
| 23 | + AptosAuthorizeUpgradeContract, |
| 24 | + CosmosUpgradeContract, |
| 25 | + EvmUpgradeContract, |
| 26 | +} from "../governance_payload/UpgradeContract"; |
| 27 | +import { |
| 28 | + AuthorizeGovernanceDataSourceTransfer, |
| 29 | + RequestGovernanceDataSourceTransfer, |
| 30 | +} from "../governance_payload/GovernanceDataSourceTransfer"; |
| 31 | +import { SetFee } from "../governance_payload/SetFee"; |
| 32 | +import { SetValidPeriod } from "../governance_payload/SetValidPeriod"; |
| 33 | +import { |
| 34 | + DataSource, |
| 35 | + SetDataSources, |
| 36 | +} from "../governance_payload/SetDataSources"; |
3 | 37 |
|
4 | 38 | test("GovernancePayload ser/de", (done) => {
|
5 | 39 | jest.setTimeout(60000);
|
@@ -121,3 +155,142 @@ test("GovernancePayload ser/de", (done) => {
|
121 | 155 |
|
122 | 156 | done();
|
123 | 157 | });
|
| 158 | + |
| 159 | +/** Fastcheck generator for arbitrary PythGovernanceHeaders */ |
| 160 | +function governanceHeaderArb(): Arbitrary<PythGovernanceHeader> { |
| 161 | + const actions = [ |
| 162 | + ...Object.keys(ExecutorAction), |
| 163 | + ...Object.keys(TargetAction), |
| 164 | + ] as ActionName[]; |
| 165 | + const actionArb = fc.constantFrom(...actions); |
| 166 | + const targetChainIdArb = fc.constantFrom( |
| 167 | + ...(Object.keys(CHAINS) as ChainName[]) |
| 168 | + ); |
| 169 | + |
| 170 | + return actionArb.chain((action) => { |
| 171 | + return targetChainIdArb.chain((chainId) => { |
| 172 | + return fc.constant(new PythGovernanceHeader(chainId, action)); |
| 173 | + }); |
| 174 | + }); |
| 175 | +} |
| 176 | + |
| 177 | +/** Fastcheck generator for arbitrary Buffers */ |
| 178 | +function bufferArb(constraints?: IntArrayConstraints): Arbitrary<Buffer> { |
| 179 | + return fc.uint8Array(constraints).map((a) => Buffer.from(a)); |
| 180 | +} |
| 181 | + |
| 182 | +/** Fastcheck generator for a uint of numBits bits. Warning: don't pass numBits > float precision */ |
| 183 | +function uintArb(numBits: number): Arbitrary<number> { |
| 184 | + return fc.bigUintN(numBits).map((x) => Number.parseInt(x.toString())); |
| 185 | +} |
| 186 | + |
| 187 | +/** Fastcheck generator for a byte array encoded as a hex string. */ |
| 188 | +function hexBytesArb(constraints?: IntArrayConstraints): Arbitrary<string> { |
| 189 | + return fc.uint8Array(constraints).map((a) => Buffer.from(a).toString("hex")); |
| 190 | +} |
| 191 | + |
| 192 | +function dataSourceArb(): Arbitrary<DataSource> { |
| 193 | + return fc.record({ |
| 194 | + emitterChain: uintArb(16), |
| 195 | + emitterAddress: hexBytesArb({ minLength: 32, maxLength: 32 }), |
| 196 | + }); |
| 197 | +} |
| 198 | + |
| 199 | +/** |
| 200 | + * Fastcheck generator for arbitrary PythGovernanceActions. |
| 201 | + * |
| 202 | + * Note that this generator doesn't generate ExecutePostedVaa instruction payloads because they're hard to generate. |
| 203 | + */ |
| 204 | +function governanceActionArb(): Arbitrary<PythGovernanceAction> { |
| 205 | + return governanceHeaderArb().chain<PythGovernanceAction>((header) => { |
| 206 | + if (header.action === "ExecutePostedVaa") { |
| 207 | + // NOTE: the instructions field is hard to generatively test, so we're using the hardcoded |
| 208 | + // tests above instead. |
| 209 | + return fc.constant(new ExecutePostedVaa(header.targetChainId, [])); |
| 210 | + } else if (header.action === "UpgradeContract") { |
| 211 | + const cosmosArb = fc.bigUintN(64).map((codeId) => { |
| 212 | + return new CosmosUpgradeContract(header.targetChainId, codeId); |
| 213 | + }); |
| 214 | + const aptosArb = hexBytesArb({ minLength: 32, maxLength: 32 }).map( |
| 215 | + (buffer) => { |
| 216 | + return new AptosAuthorizeUpgradeContract( |
| 217 | + header.targetChainId, |
| 218 | + buffer |
| 219 | + ); |
| 220 | + } |
| 221 | + ); |
| 222 | + const evmArb = hexBytesArb({ minLength: 20, maxLength: 20 }).map( |
| 223 | + (address) => { |
| 224 | + return new EvmUpgradeContract(header.targetChainId, address); |
| 225 | + } |
| 226 | + ); |
| 227 | + |
| 228 | + return fc.oneof(cosmosArb, aptosArb, evmArb); |
| 229 | + } else if (header.action === "AuthorizeGovernanceDataSourceTransfer") { |
| 230 | + return bufferArb().map((claimVaa) => { |
| 231 | + return new AuthorizeGovernanceDataSourceTransfer( |
| 232 | + header.targetChainId, |
| 233 | + claimVaa |
| 234 | + ); |
| 235 | + }); |
| 236 | + } else if (header.action === "SetDataSources") { |
| 237 | + return fc.array(dataSourceArb()).map((dataSources) => { |
| 238 | + return new SetDataSources(header.targetChainId, dataSources); |
| 239 | + }); |
| 240 | + } else if (header.action === "SetFee") { |
| 241 | + return fc |
| 242 | + .record({ v: fc.bigUintN(64), e: fc.bigUintN(64) }) |
| 243 | + .map(({ v, e }) => { |
| 244 | + return new SetFee(header.targetChainId, v, e); |
| 245 | + }); |
| 246 | + } else if (header.action === "SetValidPeriod") { |
| 247 | + return fc.bigUintN(64).map((period) => { |
| 248 | + return new SetValidPeriod(header.targetChainId, period); |
| 249 | + }); |
| 250 | + } else if (header.action === "RequestGovernanceDataSourceTransfer") { |
| 251 | + return fc.bigUintN(32).map((index) => { |
| 252 | + return new RequestGovernanceDataSourceTransfer( |
| 253 | + header.targetChainId, |
| 254 | + parseInt(index.toString()) |
| 255 | + ); |
| 256 | + }); |
| 257 | + } else { |
| 258 | + throw new Error("Unsupported action type"); |
| 259 | + } |
| 260 | + }); |
| 261 | +} |
| 262 | + |
| 263 | +test("Header serialization round-trip test", (done) => { |
| 264 | + fc.assert( |
| 265 | + fc.property(governanceHeaderArb(), (original) => { |
| 266 | + const decoded = PythGovernanceHeader.decode(original.encode()); |
| 267 | + if (decoded === undefined) { |
| 268 | + return false; |
| 269 | + } |
| 270 | + |
| 271 | + return ( |
| 272 | + decoded.action === original.action && |
| 273 | + decoded.targetChainId === original.targetChainId |
| 274 | + ); |
| 275 | + }) |
| 276 | + ); |
| 277 | + |
| 278 | + done(); |
| 279 | +}); |
| 280 | + |
| 281 | +test("Governance action serialization round-trip test", (done) => { |
| 282 | + fc.assert( |
| 283 | + fc.property(governanceActionArb(), (original) => { |
| 284 | + const encoded = original.encode(); |
| 285 | + const decoded = decodeGovernancePayload(encoded); |
| 286 | + if (decoded === undefined) { |
| 287 | + return false; |
| 288 | + } |
| 289 | + |
| 290 | + // TODO: not sure if i love this test. |
| 291 | + return decoded.encode().equals(original.encode()); |
| 292 | + }) |
| 293 | + ); |
| 294 | + |
| 295 | + done(); |
| 296 | +}); |
0 commit comments