diff --git a/contrib/core-contract-tests/package-lock.json b/contrib/core-contract-tests/package-lock.json index e5c3e22e1..1fcb7e7f7 100644 --- a/contrib/core-contract-tests/package-lock.json +++ b/contrib/core-contract-tests/package-lock.json @@ -10,8 +10,9 @@ "license": "ISC", "dependencies": { "@hirosystems/clarinet-sdk": "^1.1.0", - "@stacks/transactions": "^6.9.0", + "@stacks/transactions": "^6.12.0", "chokidar-cli": "^3.0.0", + "fast-check": "^3.15.1", "typescript": "^5.2.2", "vite": "^4.4.9", "vitest": "^0.34.4", @@ -416,40 +417,40 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, "node_modules/@stacks/common": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/@stacks/common/-/common-6.8.1.tgz", - "integrity": "sha512-ewL9GLZNQYa5a/3K4xSHlHIgHkD4rwWW/QEaPId8zQIaL+1O9qCaF4LX9orNQeOmEk8kvG0x2xGV54fXKCZeWQ==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@stacks/common/-/common-6.10.0.tgz", + "integrity": "sha512-6x5Z7AKd9/kj3+DYE9xIDIkFLHihBH614i2wqrZIjN02WxVo063hWSjIlUxlx8P4gl6olVzlOy5LzhLJD9OP0A==", "dependencies": { "@types/bn.js": "^5.1.0", "@types/node": "^18.0.4" } }, "node_modules/@stacks/network": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.8.1.tgz", - "integrity": "sha512-n8M25pPbLqpSBctabtsLOTBlmPvm9EPQpTI//x7HLdt5lEjDXxauEQt0XGSvDUZwecrmztqt9xNxlciiGApRBw==", + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", "dependencies": { - "@stacks/common": "^6.8.1", + "@stacks/common": "^6.10.0", "cross-fetch": "^3.1.5" } }, "node_modules/@stacks/transactions": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.9.0.tgz", - "integrity": "sha512-hSs9+0Ew++GwMZMgPObOx0iVCQRxkiCqI+DHdPEikAmg2utpyLh2/txHOjfSIkQHvcBfJJ6O5KphmxDP4gUqiA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.12.0.tgz", + "integrity": "sha512-gRP3SfTaAIoTdjMvOiLrMZb/senqB8JQlT5Y4C3/CiHhiprYwTx7TbOCSa7WsNOU99H4aNfHvatmymuggXQVkA==", "dependencies": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", - "@stacks/common": "^6.8.1", - "@stacks/network": "^6.8.1", + "@stacks/common": "^6.10.0", + "@stacks/network": "^6.11.3", "c32check": "^2.0.0", "lodash.clonedeep": "^4.5.0" } }, "node_modules/@types/bn.js": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.4.tgz", - "integrity": "sha512-ZtBd9L8hVtoBpPMSWfbwjC4dhQtJdlPS+e1A0Rydb7vg7bDcUwiRklPx24sMYtXcmAMST/k0Wze7JLbNU/5SkA==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "dependencies": { "@types/node": "*" } @@ -955,6 +956,27 @@ "node": ">=6" } }, + "node_modules/fast-check": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.15.1.tgz", + "integrity": "sha512-GutOXZ+SCxGaFWfHe0Pbeq8PrkpGtPxA9/hdkI3s9YzqeMlrq5RdJ+QfYZ/S93jMX+tAyqgW0z5c9ppD+vkGUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "dependencies": { + "pure-rand": "^6.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -1344,6 +1366,21 @@ "node": ">= 6" } }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", diff --git a/contrib/core-contract-tests/package.json b/contrib/core-contract-tests/package.json index 2f11d8736..561c7ab2c 100644 --- a/contrib/core-contract-tests/package.json +++ b/contrib/core-contract-tests/package.json @@ -10,8 +10,9 @@ "license": "ISC", "dependencies": { "@hirosystems/clarinet-sdk": "^1.1.0", - "@stacks/transactions": "^6.9.0", + "@stacks/transactions": "^6.12.0", "chokidar-cli": "^3.0.0", + "fast-check": "^3.15.1", "typescript": "^5.2.2", "vite": "^4.4.9", "vitest": "^0.34.4", diff --git a/contrib/core-contract-tests/tests/pox-4/signers-voting.prop.test.ts b/contrib/core-contract-tests/tests/pox-4/signers-voting.prop.test.ts new file mode 100644 index 000000000..0dc8ea217 --- /dev/null +++ b/contrib/core-contract-tests/tests/pox-4/signers-voting.prop.test.ts @@ -0,0 +1,130 @@ +import fc from "fast-check"; +import { assert, expect, it } from "vitest"; +import { Cl, ClarityType, isClarityType } from "@stacks/transactions"; + +it("should return correct reward-cycle-to-burn-height", () => { + fc.assert( + fc.property( + fc.constantFrom(...simnet.getAccounts().values()), + fc.nat(), + (account: string, reward_cycle: number) => { + // Arrange + const { result: pox_4_info } = simnet.callReadOnlyFn( + "pox-4", + "get-pox-info", + [], + account, + ); + assert(isClarityType(pox_4_info, ClarityType.ResponseOk)); + assert(isClarityType(pox_4_info.value, ClarityType.Tuple)); + const first_burnchain_block_height = + pox_4_info.value.data["first-burnchain-block-height"]; + const reward_cycle_length = + pox_4_info.value.data["reward-cycle-length"]; + + // Act + const { result: actual } = simnet.callReadOnlyFn( + "signers-voting", + "reward-cycle-to-burn-height", + [Cl.uint(reward_cycle)], + account, + ); + + // Assert + assert(isClarityType(reward_cycle_length, ClarityType.UInt)); + assert(isClarityType(first_burnchain_block_height, ClarityType.UInt)); + const expected = (reward_cycle * Number(reward_cycle_length.value)) + + Number(first_burnchain_block_height.value); + expect(actual).toBeUint(expected); + }, + ), + { numRuns: 250 }, + ); +}); + +it("should return correct burn-height-to-reward-cycle", () => { + fc.assert( + fc.property( + fc.constantFrom(...simnet.getAccounts().values()), + fc.nat(), + (account: string, height: number) => { + // Arrange + const { result: pox_4_info } = simnet.callReadOnlyFn( + "pox-4", + "get-pox-info", + [], + account, + ); + assert(isClarityType(pox_4_info, ClarityType.ResponseOk)); + assert(isClarityType(pox_4_info.value, ClarityType.Tuple)); + const first_burnchain_block_height = + pox_4_info.value.data["first-burnchain-block-height"]; + const reward_cycle_length = + pox_4_info.value.data["reward-cycle-length"]; + + // Act + const { result: actual } = simnet.callReadOnlyFn( + "signers-voting", + "burn-height-to-reward-cycle", + [Cl.uint(height)], + account, + ); + + // Assert + assert(isClarityType(first_burnchain_block_height, ClarityType.UInt)); + assert(isClarityType(reward_cycle_length, ClarityType.UInt)); + const expected = Math.floor( + (height - Number(first_burnchain_block_height.value)) / + Number(reward_cycle_length.value), + ); + expect(actual).toBeUint(expected); + }, + ), + { numRuns: 250 }, + ); +}); + +it("should return correct is-in-prepare-phase", () => { + fc.assert( + fc.property( + fc.constantFrom(...simnet.getAccounts().values()), + fc.nat(), + (account: string, height: number) => { + // Arrange + const { result: pox_4_info } = simnet.callReadOnlyFn( + "pox-4", + "get-pox-info", + [], + account, + ); + assert(isClarityType(pox_4_info, ClarityType.ResponseOk)); + assert(isClarityType(pox_4_info.value, ClarityType.Tuple)); + const first_burnchain_block_height = + pox_4_info.value.data["first-burnchain-block-height"]; + const prepare_cycle_length = + pox_4_info.value.data["prepare-cycle-length"]; + const reward_cycle_length = + pox_4_info.value.data["reward-cycle-length"]; + + // Act + const { result: actual } = simnet.callReadOnlyFn( + "signers-voting", + "is-in-prepare-phase", + [Cl.uint(height)], + account, + ); + + // Assert + assert(isClarityType(first_burnchain_block_height, ClarityType.UInt)); + assert(isClarityType(prepare_cycle_length, ClarityType.UInt)); + assert(isClarityType(reward_cycle_length, ClarityType.UInt)); + const expected = ((height - Number(first_burnchain_block_height.value) + + Number(prepare_cycle_length.value)) % + Number(reward_cycle_length.value)) < + Number(prepare_cycle_length.value); + expect(actual).toBeBool(expected); + }, + ), + { numRuns: 250 }, + ); +});