Skip to content

Commit 520c761

Browse files
committed
fix: clean up and add tests for airdrop contracts
1 parent 9891329 commit 520c761

File tree

7 files changed

+356
-28
lines changed

7 files changed

+356
-28
lines changed

Clarinet.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ cache_dir = './.cache'
88
[[project.requirements]]
99
contract_id = 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait'
1010

11+
[contracts.external-proxy]
12+
path = 'contracts/test-proxy.clar'
13+
deployer = 'wallet_1'
14+
clarity_version = 2
15+
epoch = 2.5
16+
17+
[contracts.proxy]
18+
path = 'contracts/test-proxy.clar'
19+
clarity_version = 2
20+
epoch = 2.5
21+
1122
[contracts.aibtcdev-airdrop-1]
1223
path = 'contracts/aibtcdev-airdrop-1.clar'
1324
clarity_version = 2

contracts/test-proxy.clar

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
;; constants
1414
;;
15+
(define-constant CONTRACT (as-contract tx-sender))
16+
(define-constant OWNER tx-sender)
1517

1618
;; data vars
1719
;;
@@ -27,10 +29,18 @@
2729
caller: contract-caller,
2830
sender: tx-sender,
2931
})
30-
(ok (contract-call? .aibtcdev-bank-account get-standard-caller))
32+
(ok (contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.aibtcdev-bank-account get-standard-caller))
3133
)
3234
)
3335

36+
(define-public (mint-aibtcdev-1 (to principal))
37+
(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.aibtcdev-airdrop-1 mint to)
38+
)
39+
40+
(define-public (mint-aibtcdev-2 (to principal))
41+
(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.aibtcdev-airdrop-2 mint to)
42+
)
43+
3444
;; read only functions
3545
;;
3646

deployments/default.simnet-plan.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,19 @@ plan:
9090
emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
9191
path: contracts/aibtcdev-bank-account.clar
9292
clarity-version: 2
93+
- emulated-contract-publish:
94+
contract-name: proxy
95+
emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
96+
path: contracts/test-proxy.clar
97+
clarity-version: 2
9398
- emulated-contract-publish:
9499
contract-name: test-proxy
95100
emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
96101
path: contracts/test-proxy.clar
97102
clarity-version: 2
103+
- emulated-contract-publish:
104+
contract-name: external-proxy
105+
emulated-sender: ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5
106+
path: contracts/test-proxy.clar
107+
clarity-version: 2
98108
epoch: "2.5"

tests/aibtcdev-airdrop-1.test.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { Tx, tx } from "@hirosystems/clarinet-sdk";
2+
import {
3+
boolCV,
4+
noneCV,
5+
principalCV,
6+
someCV,
7+
uintCV,
8+
} from "@stacks/transactions";
9+
import { describe, expect, it } from "vitest";
10+
11+
const accounts = simnet.getAccounts();
12+
const address1 = accounts.get("wallet_1")!;
13+
14+
const CONTRACT = "aibtcdev-airdrop-1";
15+
16+
const getLastTokenId = () => {
17+
return simnet.callReadOnlyFn(
18+
CONTRACT,
19+
"get-last-token-id",
20+
[],
21+
simnet.deployer
22+
).result;
23+
};
24+
25+
const getOwner = (id: number) => {
26+
return simnet.callReadOnlyFn(
27+
CONTRACT,
28+
"get-owner",
29+
[uintCV(id)],
30+
simnet.deployer
31+
).result;
32+
};
33+
34+
const transfer = (id: number, from: string, to: string, sender: string) => {
35+
return tx.callPublicFn(
36+
CONTRACT,
37+
"transfer",
38+
[uintCV(id), principalCV(from), principalCV(to)],
39+
sender
40+
);
41+
};
42+
43+
const mint = (to: string, sender: string | string = simnet.deployer) => {
44+
return tx.callPublicFn(CONTRACT, "mint", [principalCV(to)], sender);
45+
};
46+
47+
describe("get-last-token-id", () => {
48+
it("returns 0 after deployment", () => {
49+
expect(getLastTokenId()).toBeOk(uintCV(0));
50+
});
51+
52+
it("returns correct value after minting few NFT's", () => {
53+
const MIN = 0;
54+
const MAX = 200;
55+
const MINTS = Math.floor(Math.random() * (MAX - MIN + 1)) + MIN;
56+
57+
const TXS = new Array(MINTS).fill(null).map(() => mint(simnet.deployer));
58+
59+
simnet.mineBlock(TXS);
60+
61+
expect(getLastTokenId()).toBeOk(uintCV(MINTS));
62+
});
63+
});
64+
65+
describe("mint", () => {
66+
it("fails when called by someone who is not contract deployer", () => {
67+
const tx = mint(address1, address1);
68+
const result = simnet.mineBlock([tx])[0].result;
69+
70+
expect(result).toBeErr(uintCV(401));
71+
});
72+
73+
it("succeeds when called by anyone via proxy contract deployed by same address as NFT contract deployer", () => {
74+
const t1 = tx.callPublicFn(
75+
"proxy",
76+
"mint-aibtcdev-1",
77+
[principalCV(address1)],
78+
address1
79+
);
80+
const t2 = tx.callPublicFn(
81+
"proxy",
82+
"mint-aibtcdev-1",
83+
[principalCV(address1)],
84+
simnet.deployer
85+
);
86+
87+
const block = simnet.mineBlock([t1, t2]);
88+
89+
expect(block[0].result).toBeOk(boolCV(true));
90+
expect(block[1].result).toBeOk(boolCV(true));
91+
92+
expect(getOwner(1)).toBeOk(someCV(principalCV(address1)));
93+
expect(getOwner(2)).toBeOk(someCV(principalCV(address1)));
94+
});
95+
96+
it("fails when called by anyone via proxy contract deployed by different address than NFT contract deployer", () => {
97+
const t1 = tx.callPublicFn(
98+
`${address1}.external-proxy`,
99+
"mint-aibtcdev-1",
100+
[principalCV(address1)],
101+
address1
102+
);
103+
const t2 = tx.callPublicFn(
104+
`${address1}.external-proxy`,
105+
"mint-aibtcdev-1",
106+
[principalCV(address1)],
107+
simnet.deployer
108+
);
109+
110+
const block = simnet.mineBlock([t1, t2]);
111+
112+
expect(block[0].result).toBeErr(uintCV(401));
113+
expect(block[1].result).toBeErr(uintCV(401));
114+
});
115+
116+
it("mints new NFT to transaction sender", () => {
117+
const t1 = mint(simnet.deployer);
118+
const t2 = mint(address1);
119+
120+
expect(getOwner(1)).toBeOk(noneCV());
121+
expect(getOwner(2)).toBeOk(noneCV());
122+
123+
simnet.mineBlock([t1, t2]);
124+
125+
expect(getOwner(1)).toBeOk(someCV(principalCV(simnet.deployer)));
126+
expect(getOwner(2)).toBeOk(someCV(principalCV(address1)));
127+
});
128+
});
129+
130+
describe("transfer", () => {
131+
it("fails when called by someone who is not NFT owner", () => {
132+
const owner = address1;
133+
const not_owner = simnet.deployer;
134+
135+
simnet.mineBlock([mint(owner)]);
136+
expect(getOwner(1)).toBeOk(someCV(principalCV(owner)));
137+
138+
const result = simnet.mineBlock([
139+
transfer(1, owner, not_owner, not_owner),
140+
])[0].result;
141+
expect(result).toBeErr(uintCV(4));
142+
});
143+
144+
it("transfers NFT from one account to another", () => {
145+
const from = address1;
146+
const to = simnet.deployer;
147+
148+
simnet.mineBlock([mint(from), mint(from)]);
149+
expect(getOwner(1)).toBeOk(someCV(principalCV(from)));
150+
expect(getOwner(2)).toBeOk(someCV(principalCV(from)));
151+
152+
const result = simnet.mineBlock([transfer(1, from, to, from)])[0].result;
153+
expect(result).toBeOk(boolCV(true));
154+
155+
expect(getOwner(1)).toBeOk(someCV(principalCV(to)));
156+
expect(getOwner(2)).toBeOk(someCV(principalCV(from)));
157+
});
158+
});

tests/aibtcdev-airdrop-2.test.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { Tx, tx } from "@hirosystems/clarinet-sdk";
2+
import {
3+
boolCV,
4+
noneCV,
5+
principalCV,
6+
someCV,
7+
uintCV,
8+
} from "@stacks/transactions";
9+
import { describe, expect, it } from "vitest";
10+
11+
const accounts = simnet.getAccounts();
12+
const address1 = accounts.get("wallet_1")!;
13+
14+
const CONTRACT = "aibtcdev-airdrop-2";
15+
16+
const getLastTokenId = () => {
17+
return simnet.callReadOnlyFn(
18+
CONTRACT,
19+
"get-last-token-id",
20+
[],
21+
simnet.deployer
22+
).result;
23+
};
24+
25+
const getOwner = (id: number) => {
26+
return simnet.callReadOnlyFn(
27+
CONTRACT,
28+
"get-owner",
29+
[uintCV(id)],
30+
simnet.deployer
31+
).result;
32+
};
33+
34+
const transfer = (id: number, from: string, to: string, sender: string) => {
35+
return tx.callPublicFn(
36+
CONTRACT,
37+
"transfer",
38+
[uintCV(id), principalCV(from), principalCV(to)],
39+
sender
40+
);
41+
};
42+
43+
const mint = (to: string, sender: string | string = simnet.deployer) => {
44+
return tx.callPublicFn(CONTRACT, "mint", [principalCV(to)], sender);
45+
};
46+
47+
describe("get-last-token-id", () => {
48+
it("returns 0 after deployment", () => {
49+
expect(getLastTokenId()).toBeOk(uintCV(0));
50+
});
51+
52+
it("returns correct value after minting few NFT's", () => {
53+
const MIN = 0;
54+
const MAX = 200;
55+
const MINTS = Math.floor(Math.random() * (MAX - MIN + 1)) + MIN;
56+
57+
const TXS = new Array(MINTS).fill(null).map(() => mint(simnet.deployer));
58+
59+
simnet.mineBlock(TXS);
60+
61+
expect(getLastTokenId()).toBeOk(uintCV(MINTS));
62+
});
63+
});
64+
65+
describe("mint", () => {
66+
it("fails when called by someone who is not contract deployer", () => {
67+
const tx = mint(address1, address1);
68+
const result = simnet.mineBlock([tx])[0].result;
69+
70+
expect(result).toBeErr(uintCV(401));
71+
});
72+
73+
it("succeeds when called by anyone via proxy contract deployed by same address as NFT contract deployer", () => {
74+
const t1 = tx.callPublicFn(
75+
"proxy",
76+
"mint-aibtcdev-2",
77+
[principalCV(address1)],
78+
address1
79+
);
80+
const t2 = tx.callPublicFn(
81+
"proxy",
82+
"mint-aibtcdev-2",
83+
[principalCV(address1)],
84+
simnet.deployer
85+
);
86+
87+
const block = simnet.mineBlock([t1, t2]);
88+
89+
expect(block[0].result).toBeOk(boolCV(true));
90+
expect(block[1].result).toBeOk(boolCV(true));
91+
92+
expect(getOwner(1)).toBeOk(someCV(principalCV(address1)));
93+
expect(getOwner(2)).toBeOk(someCV(principalCV(address1)));
94+
});
95+
96+
it("fails when called by anyone via proxy contract deployed by different address than NFT contract deployer", () => {
97+
const t1 = tx.callPublicFn(
98+
`${address1}.external-proxy`,
99+
"mint-aibtcdev-2",
100+
[principalCV(address1)],
101+
address1
102+
);
103+
const t2 = tx.callPublicFn(
104+
`${address1}.external-proxy`,
105+
"mint-aibtcdev-2",
106+
[principalCV(address1)],
107+
simnet.deployer
108+
);
109+
110+
const block = simnet.mineBlock([t1, t2]);
111+
112+
expect(block[0].result).toBeErr(uintCV(401));
113+
expect(block[1].result).toBeErr(uintCV(401));
114+
});
115+
116+
it("mints new NFT to transaction sender", () => {
117+
const t1 = mint(simnet.deployer);
118+
const t2 = mint(address1);
119+
120+
expect(getOwner(1)).toBeOk(noneCV());
121+
expect(getOwner(2)).toBeOk(noneCV());
122+
123+
simnet.mineBlock([t1, t2]);
124+
125+
expect(getOwner(1)).toBeOk(someCV(principalCV(simnet.deployer)));
126+
expect(getOwner(2)).toBeOk(someCV(principalCV(address1)));
127+
});
128+
});
129+
130+
describe("transfer", () => {
131+
it("fails when called by someone who is not NFT owner", () => {
132+
const owner = address1;
133+
const not_owner = simnet.deployer;
134+
135+
simnet.mineBlock([mint(owner)]);
136+
expect(getOwner(1)).toBeOk(someCV(principalCV(owner)));
137+
138+
const result = simnet.mineBlock([
139+
transfer(1, owner, not_owner, not_owner),
140+
])[0].result;
141+
expect(result).toBeErr(uintCV(4));
142+
});
143+
144+
it("transfers NFT from one account to another", () => {
145+
const from = address1;
146+
const to = simnet.deployer;
147+
148+
simnet.mineBlock([mint(from), mint(from)]);
149+
expect(getOwner(1)).toBeOk(someCV(principalCV(from)));
150+
expect(getOwner(2)).toBeOk(someCV(principalCV(from)));
151+
152+
const result = simnet.mineBlock([transfer(1, from, to, from)])[0].result;
153+
expect(result).toBeOk(boolCV(true));
154+
155+
expect(getOwner(1)).toBeOk(someCV(principalCV(to)));
156+
expect(getOwner(2)).toBeOk(someCV(principalCV(from)));
157+
});
158+
});

0 commit comments

Comments
 (0)