tests: migrate core contracts test to clarinet-sdk

This commit is contained in:
Hugo Caillard
2023-11-03 18:00:48 +01:00
parent 6f04ad818b
commit 00d5a36ff4
9 changed files with 2642 additions and 573 deletions

View File

@@ -145,12 +145,15 @@ jobs:
uses: actions/checkout@v3
- name: Execute core contract unit tests in Clarinet
id: clarinet_unit_test
uses: docker://hirosystems/clarinet:1.1.0
uses: actions/setup-node@v3
with:
args: test --coverage --manifest-path=./contrib/core-contract-tests/Clarinet.toml
node-version: 18.x
cache: "npm"
- run: npm ci
- run: npm test
- name: Export code coverage
id: clarinet_codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage.lcov
files: ./lcov.info
verbose: true

144
Cargo.lock generated
View File

@@ -450,6 +450,28 @@ dependencies = [
"serde",
]
[[package]]
name = "bindgen"
version = "0.64.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
dependencies = [
"bitflags 1.3.2",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 1.0.109",
"which",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -584,6 +606,15 @@ version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
@@ -636,6 +667,17 @@ dependencies = [
"inout",
]
[[package]]
name = "clang-sys"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.34.0"
@@ -1417,6 +1459,12 @@ version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "gloo-timers"
version = "0.2.6"
@@ -1878,10 +1926,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.140"
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
[[package]]
name = "libflate"
@@ -1903,6 +1957,16 @@ dependencies = [
"rle-decode-fast",
]
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if 1.0.0",
"winapi 0.3.9",
]
[[package]]
name = "libsigner"
version = "0.0.1"
@@ -1963,6 +2027,12 @@ version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "linux-raw-sys"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
[[package]]
name = "log"
version = "0.4.17"
@@ -2022,6 +2092,12 @@ dependencies = [
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.6.2"
@@ -2134,6 +2210,16 @@ dependencies = [
"memoffset 0.6.5",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -2268,7 +2354,10 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "p256k1"
version = "5.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e81c2cb5a1936d3f26278f9d698932239d03ddf0d5818392d91cd5f98ffc79"
dependencies = [
"bindgen",
"bitvec",
"bs58 0.4.0",
"cc",
@@ -2317,6 +2406,12 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "percent-encoding"
version = "2.2.0"
@@ -2879,6 +2974,12 @@ version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hex"
version = "2.1.0"
@@ -2935,10 +3036,23 @@ dependencies = [
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"linux-raw-sys 0.3.8",
"windows-sys 0.45.0",
]
[[package]]
name = "rustix"
version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys 0.4.10",
"windows-sys 0.48.0",
]
[[package]]
name = "rustls"
version = "0.21.7"
@@ -3283,6 +3397,12 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]]
name = "simple-mutex"
version = "1.1.5"
@@ -3671,7 +3791,7 @@ dependencies = [
"cfg-if 1.0.0",
"fastrand",
"redox_syscall 0.3.5",
"rustix",
"rustix 0.37.7",
"windows-sys 0.45.0",
]
@@ -4352,6 +4472,18 @@ version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
[[package]]
name = "which"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix 0.38.21",
]
[[package]]
name = "winapi"
version = "0.2.8"
@@ -4574,6 +4706,8 @@ dependencies = [
[[package]]
name = "wsts"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0c0ec44cbd35be82490c8c566ad4971f7b41ffe8508f1c9938140df7fe18b2"
dependencies = [
"aes-gcm 0.10.2",
"bs58 0.5.0",

View File

@@ -0,0 +1,7 @@
logs
*.log
npm-debug.log*
coverage
*.info
costs-reports.json
node_modules

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
{
"name": "core-contract-tests-tests",
"version": "1.0.0",
"description": "Run unit tests on this project.",
"private": true,
"scripts": {
"test": "vitest run -- --coverage"
},
"author": "",
"license": "ISC",
"dependencies": {
"@hirosystems/clarinet-sdk": "^1.1.0",
"@stacks/transactions": "^6.9.0",
"chokidar-cli": "^3.0.0",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vitest": "^0.34.4",
"vitest-environment-clarinet": "^1.0.0"
}
}

View File

@@ -0,0 +1,597 @@
import { Cl } from "@stacks/transactions";
import { beforeEach, describe, expect, it } from "vitest";
import { createHash } from "node:crypto";
const accounts = simnet.getAccounts();
const alice = accounts.get("wallet_1")!;
const bob = accounts.get("wallet_2")!;
const charlie = accounts.get("wallet_3")!;
const cases = [
{
namespace: "blockstack",
version: 1,
salt: "0000",
value: 640000000,
namespaceOwner: alice,
nameOwner: bob,
priceFunction: [
Cl.uint(4), // base
Cl.uint(250), // coeff
Cl.uint(7), // bucket 1
Cl.uint(6), // bucket 2
Cl.uint(5), // bucket 3
Cl.uint(4), // bucket 4
Cl.uint(3), // bucket 5
Cl.uint(2), // bucket 6
Cl.uint(1), // bucket 7
Cl.uint(1), // bucket 8
Cl.uint(1), // bucket 9
Cl.uint(1), // bucket 10
Cl.uint(1), // bucket 11
Cl.uint(1), // bucket 12
Cl.uint(1), // bucket 13
Cl.uint(1), // bucket 14
Cl.uint(1), // bucket 15
Cl.uint(1), // bucket 16+
Cl.uint(4), // nonAlphaDiscount
Cl.uint(4), // noVowelDiscount
],
renewalRule: 10,
nameImporter: alice,
zonefile: "0000",
},
{
namespace: "id",
version: 1,
salt: "0000",
value: 64000000000,
namespaceOwner: alice,
nameOwner: bob,
priceFunction: [
Cl.uint(4), // base
Cl.uint(250), // coeff
Cl.uint(6), // bucket 1
Cl.uint(5), // bucket 2
Cl.uint(4), // bucket 3
Cl.uint(3), // bucket 4
Cl.uint(2), // bucket 5
Cl.uint(1), // bucket 6
Cl.uint(0), // bucket 7
Cl.uint(0), // bucket 8
Cl.uint(0), // bucket 9
Cl.uint(0), // bucket 10
Cl.uint(0), // bucket 11
Cl.uint(0), // bucket 12
Cl.uint(0), // bucket 13
Cl.uint(0), // bucket 14
Cl.uint(0), // bucket 15
Cl.uint(0), // bucket 16+
Cl.uint(20), // nonAlphaDiscount
Cl.uint(20), // noVowelDiscount
],
renewalRule: 20,
nameImporter: alice,
zonefile: "1111",
},
];
describe("test bns contract namespace errors", () => {
it("should throw ERR_NAMESPACE_BLANK", () => {
const { result } = simnet.callReadOnlyFn(
"bns",
"resolve-principal",
[Cl.standardPrincipal(bob)],
alice
);
expect(result).toBeErr(
Cl.tuple({
code: Cl.int(2013),
name: Cl.none(),
})
);
});
it("should throw ERR_NAMESPACE_NOT_FOUND", () => {
const { result } = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[1].namespace),
Cl.bufferFromUtf8("bob"),
Cl.bufferFromUtf8(cases[1].salt),
Cl.bufferFromUtf8(cases[1].zonefile),
],
cases[0].nameOwner
);
expect(result).toBeErr(Cl.int(1005));
});
});
describe("preorder namespace", () => {
it("should preorder a namespace", () => {
const merged = new TextEncoder().encode(`${cases[1].namespace}${cases[1].salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
const { result } = simnet.callPublicFn(
"bns",
"namespace-preorder",
[Cl.buffer(ripemd160), Cl.uint(cases[1].value)],
cases[1].namespaceOwner
);
expect(result).toBeOk(Cl.uint(144 + simnet.blockHeight));
});
});
describe("namespace reveal workflow", () => {
// preorder namespace
beforeEach(() => {
const merged = new TextEncoder().encode(`${cases[1].namespace}${cases[1].salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
const { result } = simnet.callPublicFn(
"bns",
"namespace-preorder",
[Cl.buffer(ripemd160), Cl.uint(cases[1].value)],
cases[1].namespaceOwner
);
expect(result).toBeOk(Cl.uint(144 + simnet.blockHeight));
});
it("should reveal a namespace", () => {
const { result } = simnet.callPublicFn(
"bns",
"namespace-reveal",
[
Cl.bufferFromUtf8(cases[1].namespace),
Cl.bufferFromUtf8(cases[1].salt),
...cases[1].priceFunction,
Cl.uint(cases[1].renewalRule),
Cl.standardPrincipal(cases[1].nameImporter),
],
cases[1].namespaceOwner
);
expect(result).toBeOk(Cl.bool(true));
});
it("fails if the namespace is not revealed", () => {
simnet.callPublicFn(
"bns",
"namespace-reveal",
[
Cl.bufferFromUtf8(cases[1].namespace),
Cl.bufferFromUtf8(cases[1].salt),
...cases[1].priceFunction,
Cl.uint(cases[1].renewalRule),
Cl.standardPrincipal(cases[1].nameImporter),
],
cases[1].namespaceOwner
);
const name = "baobab";
const merged = new TextEncoder().encode(`${name}.${cases[1].namespace}${cases[1].salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
const preorder = simnet.callPublicFn(
"bns",
"name-preorder",
[Cl.buffer(ripemd160), Cl.uint(100)],
bob
);
expect(preorder.result).toBeOk(Cl.uint(144 + simnet.blockHeight));
const register = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[1].namespace),
Cl.bufferFromUtf8(name),
Cl.bufferFromUtf8(cases[1].salt),
Cl.bufferFromUtf8(cases[1].zonefile),
],
bob
);
expect(register.result).toBeErr(Cl.int(2004));
});
it("can launch a namespace", () => {
const merged = new TextEncoder().encode(`${cases[0].namespace}${cases[0].salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
simnet.callPublicFn(
"bns",
"namespace-preorder",
[Cl.buffer(ripemd160), Cl.uint(cases[0].value)],
cases[0].namespaceOwner
);
simnet.callPublicFn(
"bns",
"namespace-reveal",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(cases[0].salt),
...cases[0].priceFunction,
Cl.uint(cases[0].renewalRule),
Cl.standardPrincipal(cases[0].nameImporter),
],
cases[0].namespaceOwner
);
const { result } = simnet.callPublicFn(
"bns",
"namespace-ready",
[Cl.bufferFromUtf8(cases[0].namespace)],
cases[0].namespaceOwner
);
expect(result).toBeOk(Cl.bool(true));
});
});
describe("name revealing workflow", () => {
beforeEach(() => {
// launch namespace
const merged = new TextEncoder().encode(`${cases[0].namespace}${cases[0].salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
simnet.callPublicFn(
"bns",
"namespace-preorder",
[Cl.buffer(ripemd160), Cl.uint(cases[0].value)],
cases[0].namespaceOwner
);
simnet.callPublicFn(
"bns",
"namespace-reveal",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(cases[0].salt),
...cases[0].priceFunction,
Cl.uint(cases[0].renewalRule),
Cl.standardPrincipal(cases[0].nameImporter),
],
cases[0].namespaceOwner
);
const { result } = simnet.callPublicFn(
"bns",
"namespace-ready",
[Cl.bufferFromUtf8(cases[0].namespace)],
cases[0].namespaceOwner
);
expect(result).toBeOk(Cl.bool(true));
});
it("should fail if no preorder", () => {
const name = "bob";
const { result } = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(name),
Cl.bufferFromUtf8(cases[0].salt),
Cl.bufferFromUtf8(cases[0].zonefile),
],
bob
);
expect(result).toBeErr(Cl.int(2001));
});
it("should fail if stx burnt is too low", () => {
const name = "bub";
const merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${cases[0].salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
simnet.callPublicFn("bns", "name-preorder", [Cl.buffer(ripemd160), Cl.uint(2559999)], bob);
const { result } = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8("bub"),
Cl.bufferFromUtf8(cases[0].salt),
Cl.bufferFromUtf8(cases[0].zonefile),
],
bob
);
expect(result).toBeErr(Cl.int(2007));
});
it("should fail if existing pre-order", () => {
const name = "Bob";
const merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${cases[0].salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
simnet.callPublicFn("bns", "name-preorder", [Cl.buffer(ripemd160), Cl.uint(2560000)], bob);
const { result } = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(name),
Cl.bufferFromUtf8(cases[0].salt),
Cl.bufferFromUtf8(cases[0].zonefile),
],
bob
);
expect(result).toBeErr(Cl.int(2022));
});
it("should successfully register", () => {
const name = "bob";
const merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${cases[0].salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
simnet.callPublicFn("bns", "name-preorder", [Cl.buffer(ripemd160), Cl.uint(2560000)], bob);
const register = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(name),
Cl.bufferFromUtf8(cases[0].salt),
Cl.bufferFromUtf8(cases[0].zonefile),
],
bob
);
expect(register.result).toBeOk(Cl.bool(true));
const resolvePrincipal = simnet.callReadOnlyFn(
"bns",
"resolve-principal",
[Cl.standardPrincipal(bob)],
alice
);
expect(resolvePrincipal.result).toBeOk(
Cl.tuple({
name: Cl.bufferFromUtf8("bob"),
namespace: Cl.bufferFromUtf8("blockstack"),
})
);
const nameResolve = simnet.callReadOnlyFn(
"bns",
"name-resolve",
[Cl.bufferFromUtf8(cases[0].namespace), Cl.bufferFromUtf8(name)],
alice
);
expect(nameResolve.result).toBeOk(
Cl.tuple({
owner: Cl.standardPrincipal(bob),
["zonefile-hash"]: Cl.bufferFromUtf8(cases[0].zonefile),
["lease-ending-at"]: Cl.some(Cl.uint(16)),
["lease-started-at"]: Cl.uint(6),
})
);
});
it("should fail registering twice", () => {
const name = "bob";
const merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${cases[0].salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
simnet.callPublicFn("bns", "name-preorder", [Cl.buffer(ripemd160), Cl.uint(2560000)], bob);
simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(name),
Cl.bufferFromUtf8(cases[0].salt),
Cl.bufferFromUtf8(cases[0].zonefile),
],
bob
);
simnet.callReadOnlyFn("bns", "resolve-principal", [Cl.standardPrincipal(bob)], alice);
const { result } = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(name),
Cl.bufferFromUtf8(cases[0].salt),
Cl.bufferFromUtf8(cases[0].zonefile),
],
bob
);
expect(result).toBeErr(Cl.int(2004));
});
});
describe("register a name again before and after expiration", () => {
beforeEach(() => {
// launch namespace
const mergedNS = new TextEncoder().encode(`${cases[0].namespace}${cases[0].salt}`);
const sha256NS = createHash("sha256").update(mergedNS).digest();
const ripemd160NS = createHash("ripemd160").update(sha256NS).digest();
simnet.callPublicFn(
"bns",
"namespace-preorder",
[Cl.buffer(ripemd160NS), Cl.uint(cases[0].value)],
cases[0].namespaceOwner
);
simnet.callPublicFn(
"bns",
"namespace-reveal",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(cases[0].salt),
...cases[0].priceFunction,
Cl.uint(cases[0].renewalRule),
Cl.standardPrincipal(cases[0].nameImporter),
],
cases[0].namespaceOwner
);
simnet.callPublicFn(
"bns",
"namespace-ready",
[Cl.bufferFromUtf8(cases[0].namespace)],
cases[0].namespaceOwner
);
// register bob.blockstack
const name = "bob";
const merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${cases[0].salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
simnet.callPublicFn("bns", "name-preorder", [Cl.buffer(ripemd160), Cl.uint(2560000)], bob);
simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(name),
Cl.bufferFromUtf8(cases[0].salt),
Cl.bufferFromUtf8(cases[0].zonefile),
],
bob
);
});
it("fails if someone else tries to register it", () => {
const name = "bob";
let salt = "1111";
const merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
const preorder = simnet.callPublicFn(
"bns",
"name-preorder",
[Cl.buffer(ripemd160), Cl.uint(2560000)],
charlie
);
expect(preorder.result).toBeOk(Cl.uint(144 + simnet.blockHeight));
const register = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(name),
Cl.bufferFromUtf8(salt),
Cl.bufferFromUtf8(cases[0].zonefile),
],
charlie
);
expect(register.result).toBeErr(Cl.int(2004));
});
it("should fail to register to register two names", () => {
const name = "bobby";
const salt = "1111";
const merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
simnet.callPublicFn("bns", "name-preorder", [Cl.buffer(ripemd160), Cl.uint(2560000)], bob);
const { result } = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromUtf8(cases[0].namespace),
Cl.bufferFromUtf8(name),
Cl.bufferFromUtf8(salt),
Cl.bufferFromUtf8(cases[0].zonefile),
],
bob
);
expect(result).toBeErr(Cl.int(3001));
});
it("should allow registering a new name after first name expiration", () => {
simnet.mineEmptyBlocks(cases[0].renewalRule + 5001);
const resolve = simnet.callReadOnlyFn(
"bns",
"resolve-principal",
[Cl.standardPrincipal(bob)],
alice
);
expect(resolve.result).toBeErr(
Cl.tuple({
code: Cl.int(2008),
name: Cl.some(
Cl.tuple({
name: Cl.bufferFromUtf8("bob"),
namespace: Cl.bufferFromUtf8("blockstack"),
})
),
})
);
const name = "bobby";
const salt = "1111";
const merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
const preorder = simnet.callPublicFn(
"bns",
"name-preorder",
[Cl.buffer(ripemd160), Cl.uint(2560000)],
bob
);
expect(preorder.result).toBeOk(Cl.uint(144 + simnet.blockHeight));
const register = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromAscii(cases[0].namespace),
Cl.bufferFromAscii(name),
Cl.bufferFromAscii(salt),
Cl.bufferFromAscii(cases[0].zonefile),
],
bob
);
expect(register.result).toBeOk(Cl.bool(true));
});
it("should allow someone else to register after expiration", () => {
simnet.mineEmptyBlocks(cases[0].renewalRule + 5001);
const name = "bob";
const salt = "2222";
const merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${salt}`);
const sha256 = createHash("sha256").update(merged).digest();
const ripemd160 = createHash("ripemd160").update(sha256).digest();
simnet.callPublicFn("bns", "name-preorder", [Cl.buffer(ripemd160), Cl.uint(2560000)], charlie);
const register = simnet.callPublicFn(
"bns",
"name-register",
[
Cl.bufferFromAscii(cases[0].namespace),
Cl.bufferFromAscii(name),
Cl.bufferFromAscii(salt),
Cl.bufferFromAscii("CHARLIE"),
],
charlie
);
expect(register.result).toBeOk(Cl.bool(true));
const resolve = simnet.callReadOnlyFn(
"bns",
"name-resolve",
[Cl.bufferFromAscii(cases[0].namespace), Cl.bufferFromAscii(name)],
alice
);
expect(resolve.result).toBeOk(
Cl.tuple({
owner: Cl.standardPrincipal(charlie),
["zonefile-hash"]: Cl.bufferFromAscii("CHARLIE"),
["lease-ending-at"]: Cl.some(Cl.uint(5029)),
["lease-started-at"]: Cl.uint(5019),
})
);
});
});

View File

@@ -1,565 +0,0 @@
import { Clarinet, Tx, Chain, Account, Contract, types } from 'https://deno.land/x/clarinet@v1.1.0/index.ts';
import { assertEquals } from "https://deno.land/std@0.90.0/testing/asserts.ts";
import { createHash } from "https://deno.land/std@0.107.0/hash/mod.ts";
Clarinet.test({
name: "Ensure that name can be registered",
async fn(chain: Chain, accounts: Map<string, Account>, contracts: Map<string, Contract>) {
const alice = accounts.get("wallet_1")!;
const bob = accounts.get("wallet_2")!;
const charlie = accounts.get("wallet_3")!;
const dave = accounts.get("wallet_4")!;
const cases = [{
namespace: "blockstack",
version: 1,
salt: "0000",
value: 640000000,
namespaceOwner: alice,
nameOwner: bob,
priceFunction: [
types.uint(4), // base
types.uint(250), // coeff
types.uint(7), // bucket 1
types.uint(6), // bucket 2
types.uint(5), // bucket 3
types.uint(4), // bucket 4
types.uint(3), // bucket 5
types.uint(2), // bucket 6
types.uint(1), // bucket 7
types.uint(1), // bucket 8
types.uint(1), // bucket 9
types.uint(1), // bucket 10
types.uint(1), // bucket 11
types.uint(1), // bucket 12
types.uint(1), // bucket 13
types.uint(1), // bucket 14
types.uint(1), // bucket 15
types.uint(1), // bucket 16+
types.uint(4), // nonAlphaDiscount
types.uint(4), // noVowelDiscount
],
renewalRule: 10,
nameImporter: alice,
zonefile: "0000",
}, {
namespace: "id",
version: 1,
salt: "0000",
value: 64000000000,
namespaceOwner: alice,
nameOwner: bob,
priceFunction: [
types.uint(4), // base
types.uint(250), // coeff
types.uint(6), // bucket 1
types.uint(5), // bucket 2
types.uint(4), // bucket 3
types.uint(3), // bucket 4
types.uint(2), // bucket 5
types.uint(1), // bucket 6
types.uint(0), // bucket 7
types.uint(0), // bucket 8
types.uint(0), // bucket 9
types.uint(0), // bucket 10
types.uint(0), // bucket 11
types.uint(0), // bucket 12
types.uint(0), // bucket 13
types.uint(0), // bucket 14
types.uint(0), // bucket 15
types.uint(0), // bucket 16+
types.uint(20), // nonAlphaDiscount
types.uint(20), // noVowelDiscount
],
renewalRule: 20,
nameImporter: alice,
zonefile: "1111",
}];
let call = chain.callReadOnlyFn("bns", "resolve-principal", [types.principal(bob.address)], alice.address)
let error:any = call.result
.expectErr()
.expectTuple();
error['code'].expectInt(2013);
// Registering a name at this point should fail, namespace have not been registered yet
let block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[1].namespace),
types.buff("bob"),
types.buff(cases[1].salt),
types.buff(cases[1].zonefile)
],
cases[0].nameOwner.address),
]);
assertEquals(block.height, 2);
block.receipts[0].result
.expectErr()
.expectInt(1005);
// Preorder a namespace
let merged = new TextEncoder().encode(`${cases[1].namespace}${cases[1].salt}`);
let sha256 = createHash("sha256")
.update(merged)
.digest();
let ripemd160 = createHash("ripemd160")
.update(sha256)
.digest();
block = chain.mineBlock([
Tx.contractCall("bns", "namespace-preorder",
[
types.buff(ripemd160),
types.uint(cases[1].value)
],
cases[1].namespaceOwner.address),
]);
assertEquals(block.height, 3);
block.receipts[0].result
.expectOk()
.expectUint(144 + block.height - 1);
// Reveal the namespace
block = chain.mineBlock([
Tx.contractCall("bns", "namespace-reveal",
[
types.buff(cases[1].namespace),
types.buff(cases[1].salt),
...cases[1].priceFunction,
types.uint(cases[1].renewalRule),
types.principal(cases[1].nameImporter.address),
],
cases[1].namespaceOwner.address),
]);
assertEquals(block.height, 4);
block.receipts[0].result
.expectOk()
.expectBool(true);
// Bob can now preorder a name
let name = "baobab";
merged = new TextEncoder().encode(`${name}.${cases[1].namespace}${cases[1].salt}`);
sha256 = createHash("sha256")
.update(merged)
.digest();
ripemd160 = createHash("ripemd160")
.update(sha256)
.digest();
block = chain.mineBlock([
Tx.contractCall("bns", "name-preorder",
[
types.buff(ripemd160),
types.uint(100),
],
bob.address),
]);
assertEquals(block.height, 5);
block.receipts[0].result
.expectOk()
.expectUint(144 + block.height - 1);
// But revealing the name should fail - the namespace was not launched yet
block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[1].namespace),
types.buff(name),
types.buff(cases[1].salt),
types.buff(cases[1].zonefile),
],
bob.address),
]);
assertEquals(block.height, 6);
block.receipts[0].result
.expectErr()
.expectInt(2004);
// // Given a launched namespace 'blockstack', owned by Alice
merged = new TextEncoder().encode(`${cases[0].namespace}${cases[0].salt}`);
sha256 = createHash("sha256")
.update(merged)
.digest();
ripemd160 = createHash("ripemd160")
.update(sha256)
.digest();
block = chain.mineBlock([
Tx.contractCall("bns", "namespace-preorder",
[
types.buff(ripemd160),
types.uint(cases[0].value)
],
cases[0].namespaceOwner.address),
]);
assertEquals(block.height, 7);
block.receipts[0].result
.expectOk()
.expectUint(144 + block.height - 1);
// Reveal the namespace
block = chain.mineBlock([
Tx.contractCall("bns", "namespace-reveal",
[
types.buff(cases[0].namespace),
types.buff(cases[0].salt),
...cases[0].priceFunction,
types.uint(cases[0].renewalRule),
types.principal(cases[0].nameImporter.address),
],
cases[0].namespaceOwner.address),
]);
assertEquals(block.height, 8);
block.receipts[0].result
.expectOk()
.expectBool(true);
// Launch the namespace
block = chain.mineBlock([
Tx.contractCall("bns", "namespace-ready",
[
types.buff(cases[0].namespace),
],
cases[0].namespaceOwner.address),
]);
assertEquals(block.height, 9);
block.receipts[0].result
.expectOk()
.expectBool(true);
// Revealing the name 'bob.blockstack'
// should fail if no matching pre-order can be found
// But revealing the name should fail - the namespace was not launched yet
name = "bob";
block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[0].namespace),
types.buff(name),
types.buff(cases[0].salt),
types.buff(cases[0].zonefile),
],
bob.address),
]);
assertEquals(block.height, 10);
block.receipts[0].result
.expectErr()
.expectInt(2001);
// Bob can now preorder a name
name = "bub";
merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${cases[0].salt}`);
sha256 = createHash("sha256")
.update(merged)
.digest();
ripemd160 = createHash("ripemd160")
.update(sha256)
.digest();
block = chain.mineBlock([
Tx.contractCall("bns", "name-preorder",
[
types.buff(ripemd160),
types.uint(2559999),
],
bob.address),
]);
assertEquals(block.height, 11);
block.receipts[0].result
.expectOk()
.expectUint(144 + block.height - 1);
// should fail
block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[0].namespace),
types.buff("bub"),
types.buff(cases[0].salt),
types.buff(cases[0].zonefile),
],
bob.address),
]);
assertEquals(block.height, 12);
block.receipts[0].result
.expectErr()
.expectInt(2007);
// Given an existing pre-order of the name 'Bob.blockstack'
name = "Bob";
merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${cases[0].salt}`);
sha256 = createHash("sha256")
.update(merged)
.digest();
ripemd160 = createHash("ripemd160")
.update(sha256)
.digest();
block = chain.mineBlock([
Tx.contractCall("bns", "name-preorder",
[
types.buff(ripemd160),
types.uint(2560000),
],
bob.address),
]);
assertEquals(block.height, 13);
block.receipts[0].result
.expectOk()
.expectUint(144 + block.height - 1);
// Bob registering the name 'Bob.blockstack' should fail
block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[0].namespace),
types.buff(name),
types.buff(cases[0].salt),
types.buff(cases[0].zonefile),
],
bob.address),
]);
assertEquals(block.height, 14);
block.receipts[0].result
.expectErr()
.expectInt(2022);
// Given an existing pre-order of the name 'bob.blockstack'
name = "bob";
merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${cases[0].salt}`);
sha256 = createHash("sha256")
.update(merged)
.digest();
ripemd160 = createHash("ripemd160")
.update(sha256)
.digest();
block = chain.mineBlock([
Tx.contractCall("bns", "name-preorder",
[
types.buff(ripemd160),
types.uint(2560000),
],
bob.address),
]);
assertEquals(block.height, 15);
block.receipts[0].result
.expectOk()
.expectUint(144 + block.height - 1);
// Bob registering the name 'bob.blockstack' should succeed
block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[0].namespace),
types.buff(name),
types.buff(cases[0].salt),
types.buff(cases[0].zonefile),
],
bob.address),
]);
assertEquals(block.height, 16);
block.receipts[0].result
.expectOk()
.expectBool(true);
call = chain.callReadOnlyFn("bns", "resolve-principal", [types.principal(bob.address)], alice.address)
let response:any = call.result
.expectOk()
.expectTuple();
response["name"].expectBuff("bob");
response["namespace"].expectBuff("blockstack");
call = chain.callReadOnlyFn("bns", "name-resolve", [types.buff(cases[0].namespace), types.buff(name)], alice.address)
response = call.result
.expectOk()
.expectTuple();
response["owner"].expectPrincipal(bob.address);
response["zonefile-hash"].expectBuff(cases[0].zonefile);
// should fail registering twice
block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[0].namespace),
types.buff(name),
types.buff(cases[0].salt),
types.buff(cases[0].zonefile),
],
bob.address),
]);
assertEquals(block.height, 17);
block.receipts[0].result
.expectErr()
.expectInt(2004);
// Charlie registering 'bob.blockstack'
// should fail
name = "bob";
let salt = "1111"
merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${salt}`);
sha256 = createHash("sha256")
.update(merged)
.digest();
ripemd160 = createHash("ripemd160")
.update(sha256)
.digest();
block = chain.mineBlock([
Tx.contractCall("bns", "name-preorder",
[
types.buff(ripemd160),
types.uint(2560000),
],
charlie.address),
]);
assertEquals(block.height, 18);
block.receipts[0].result
.expectOk()
.expectUint(144 + block.height - 1);
// Bob registering the name 'bob.blockstack' should succeed
block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[0].namespace),
types.buff(name),
types.buff(salt),
types.buff(cases[0].zonefile),
],
charlie.address),
]);
assertEquals(block.height, 19);
block.receipts[0].result
.expectErr()
.expectInt(2004);
// Bob registering a second name 'bobby.blockstack'
// should fail if 'bob.blockstack' is not expired
name = "bobby";
salt = "1111"
merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${salt}`);
sha256 = createHash("sha256")
.update(merged)
.digest();
ripemd160 = createHash("ripemd160")
.update(sha256)
.digest();
block = chain.mineBlock([
Tx.contractCall("bns", "name-preorder",
[
types.buff(ripemd160),
types.uint(2560000),
],
bob.address),
]);
assertEquals(block.height, 20);
block.receipts[0].result
.expectOk()
.expectUint(144 + block.height - 1);
// Bob registering the name 'bob.blockstack' should succeed
block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[0].namespace),
types.buff(name),
types.buff(salt),
types.buff(cases[0].zonefile),
],
bob.address),
]);
assertEquals(block.height, 21);
block.receipts[0].result
.expectErr()
.expectInt(3001);
// should succeed once 'bob.blockstack' is expired
chain.mineEmptyBlock(cases[0].renewalRule + 5000);
call = chain.callReadOnlyFn("bns", "resolve-principal", [types.principal(bob.address)], alice.address)
response = call.result
.expectErr()
.expectTuple();
response["code"].expectInt("2008"); // Indicates ERR_NAME_EXPIRED
let inner:any = response["name"].expectSome().expectTuple();
inner["name"].expectBuff("bob");
inner["namespace"].expectBuff("blockstack");
name = "bobby";
salt = "1111"
merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${salt}`);
sha256 = createHash("sha256")
.update(merged)
.digest();
ripemd160 = createHash("ripemd160")
.update(sha256)
.digest();
block = chain.mineBlock([
Tx.contractCall("bns", "name-preorder",
[
types.buff(ripemd160),
types.uint(2560000),
],
bob.address),
]);
block.receipts[0].result
.expectOk()
.expectUint(144 + block.height - 1);
// Bob registering the name 'bobby.blockstack' should succeed
block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[0].namespace),
types.buff(name),
types.buff(salt),
types.buff(cases[0].zonefile),
],
bob.address),
]);
block.receipts[0].result
.expectOk()
.expectBool(true);
// Charlie registering 'bob.blockstack'
// should succeed once 'bob.blockstack' is expired
name = "bob";
salt = "2222"
merged = new TextEncoder().encode(`${name}.${cases[0].namespace}${salt}`);
sha256 = createHash("sha256")
.update(merged)
.digest();
ripemd160 = createHash("ripemd160")
.update(sha256)
.digest();
block = chain.mineBlock([
Tx.contractCall("bns", "name-preorder",
[
types.buff(ripemd160),
types.uint(2560000),
],
charlie.address),
]);
block.receipts[0].result
.expectOk()
.expectUint(144 + block.height - 1);
block = chain.mineBlock([
Tx.contractCall("bns", "name-register",
[
types.buff(cases[0].namespace),
types.buff(name),
types.buff(salt),
types.buff("CHARLIE"),
],
charlie.address),
]);
block.receipts[0].result
.expectOk()
.expectBool(true);
call = chain.callReadOnlyFn("bns", "name-resolve", [types.buff(cases[0].namespace), types.buff(name)], alice.address)
response = call.result
.expectOk()
.expectTuple();
response["owner"].expectPrincipal(charlie.address);
response["zonefile-hash"].expectBuff("CHARLIE");
},
});

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"node_modules/@hirosystems/clarinet-sdk/vitest-helpers/src",
"tests"
]
}

View File

@@ -0,0 +1,36 @@
/// <reference types="vitest" />
import { defineConfig } from "vite";
import { vitestSetupFilePath, getClarinetVitestsArgv } from "@hirosystems/clarinet-sdk/vitest";
/*
In this file, Vitest is configured so that it works seamlessly with Clarinet and the Simnet.
The `vitest-environment-clarinet` will initialise the clarinet-sdk
and make the `simnet` object available globally in the test files.
`vitestSetupFilePath` points to a file in the `@hirosystems/clarinet-sdk` package that does two things:
- run `before` hooks to initialize the simnet and `after` hooks to collect costs and coverage reports.
- load custom vitest matchers to work with Clarity values (such as `expect(...).toBeUint()`)
The `getClarinetVitestsArgv()` will parse options passed to the command `vitest run --`
- vitest run -- --manifest ./Clarinet.toml # pass a custom path
- vitest run -- --coverage --costs # collect coverage and cost reports
*/
export default defineConfig({
test: {
environment: "clarinet", // use vitest-environment-clarinet
singleThread: true,
setupFiles: [
vitestSetupFilePath,
// custom setup files can be added here
],
environmentOptions: {
clarinet: {
...getClarinetVitestsArgv(),
// add or override options
},
},
},
});