Skip to content

Commit f995f26

Browse files
authored
Merge pull request #7 from aibtcdev/feat/ad-update-sprint
NFTs as an ad network
2 parents 7e23555 + 520c761 commit f995f26

9 files changed

+505
-30
lines changed

Clarinet.toml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,31 @@ description = ''
44
authors = []
55
telemetry = true
66
cache_dir = './.cache'
7-
requirements = []
7+
8+
[[project.requirements]]
9+
contract_id = 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait'
10+
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+
22+
[contracts.aibtcdev-airdrop-1]
23+
path = 'contracts/aibtcdev-airdrop-1.clar'
24+
clarity_version = 2
25+
epoch = 2.5
26+
27+
[contracts.aibtcdev-airdrop-2]
28+
path = 'contracts/aibtcdev-airdrop-2.clar'
29+
clarity_version = 2
30+
epoch = 2.5
31+
832
[contracts.aibtcdev-aibtc]
933
path = 'contracts/aibtcdev-aibtc.clar'
1034
clarity_version = 2

contracts/aibtcdev-airdrop-1.clar

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
(impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)
2+
3+
(define-constant DEPLOYER tx-sender)
4+
5+
(define-data-var nextId uint u1)
6+
(define-data-var url (string-ascii 256) "https://nft-ad-1.aibtc.dev/aibtcdev-1.json")
7+
8+
(define-non-fungible-token aibtcdev-1 uint)
9+
10+
(define-read-only (get-last-token-id) (ok (- (var-get nextId) u1)))
11+
(define-read-only (get-token-uri (id uint)) (ok (some (var-get url) )))
12+
(define-read-only (get-owner (id uint)) (ok (nft-get-owner? aibtcdev-1 id)))
13+
14+
(define-public (transfer (id uint) (from principal) (to principal))
15+
(if (or (is-eq from tx-sender) (is-eq from contract-caller))
16+
(nft-transfer? aibtcdev-1 id from to)
17+
(err u4)
18+
)
19+
)
20+
21+
(define-public (burn (id uint) (from principal))
22+
(if (or (is-eq from tx-sender) (is-eq from contract-caller))
23+
(nft-burn? aibtcdev-1 id from)
24+
(err u4)
25+
)
26+
)
27+
28+
(define-public (mint (to principal))
29+
(let ((id (var-get nextId)))
30+
(asserts! (is-eq DEPLOYER (get-standard-caller)) (err u401))
31+
(var-set nextId (+ id u1))
32+
(nft-mint? aibtcdev-1 id to)
33+
)
34+
)
35+
36+
(define-public (set-url (new (string-ascii 256)))
37+
(if (is-eq DEPLOYER (get-standard-caller))
38+
(ok (var-set url new))
39+
(err u401)
40+
)
41+
)
42+
43+
(define-public (airdrop (l1 (list 5000 principal)) (l2 (list 5000 principal)) (l3 (list 4995 principal)))
44+
(if (is-eq DEPLOYER (get-standard-caller))
45+
(ok (var-set nextId (fold drop l3 (fold drop l2 (fold drop l1 (var-get nextId))))))
46+
(err u401)
47+
)
48+
)
49+
50+
(define-private (drop (to principal) (id uint))
51+
(begin (is-err (nft-mint? aibtcdev-1 id to)) (+ id u1))
52+
)
53+
54+
(define-read-only (get-standard-caller)
55+
(let ((d (unwrap-panic (principal-destruct? contract-caller))))
56+
(unwrap-panic (principal-construct? (get version d) (get hash-bytes d)))
57+
)
58+
)

contracts/aibtcdev-airdrop-2.clar

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
(impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)
2+
3+
(define-constant DEPLOYER tx-sender)
4+
5+
(define-data-var nextId uint u1)
6+
(define-data-var url (string-ascii 256) "https://nft-ad-2.aibtc.dev/aibtcdev-2.json")
7+
8+
(define-non-fungible-token aibtcdev-2 uint)
9+
10+
(define-read-only (get-last-token-id) (ok (- (var-get nextId) u1)))
11+
(define-read-only (get-token-uri (id uint)) (ok (some (var-get url) )))
12+
(define-read-only (get-owner (id uint)) (ok (nft-get-owner? aibtcdev-2 id)))
13+
14+
(define-public (transfer (id uint) (from principal) (to principal))
15+
(if (or (is-eq from tx-sender) (is-eq from contract-caller))
16+
(nft-transfer? aibtcdev-2 id from to)
17+
(err u4)
18+
)
19+
)
20+
21+
(define-public (burn (id uint) (from principal))
22+
(if (or (is-eq from tx-sender) (is-eq from contract-caller))
23+
(nft-burn? aibtcdev-2 id from)
24+
(err u4)
25+
)
26+
)
27+
28+
(define-public (mint (to principal))
29+
(let ((id (var-get nextId)))
30+
(asserts! (is-eq DEPLOYER (get-standard-caller)) (err u401))
31+
(var-set nextId (+ id u1))
32+
(nft-mint? aibtcdev-2 id to)
33+
)
34+
)
35+
36+
(define-public (set-url (new (string-ascii 256)))
37+
(if (is-eq DEPLOYER (get-standard-caller))
38+
(ok (var-set url new))
39+
(err u401)
40+
)
41+
)
42+
43+
(define-public (airdrop (l1 (list 5000 principal)) (l2 (list 5000 principal)) (l3 (list 4995 principal)))
44+
(if (is-eq DEPLOYER (get-standard-caller))
45+
(ok (var-set nextId (fold drop l3 (fold drop l2 (fold drop l1 (var-get nextId))))))
46+
(err u401)
47+
)
48+
)
49+
50+
(define-private (drop (to principal) (id uint))
51+
(begin (is-err (nft-mint? aibtcdev-2 id to)) (+ id u1))
52+
)
53+
54+
(define-read-only (get-standard-caller)
55+
(let ((d (unwrap-panic (principal-destruct? contract-caller))))
56+
(unwrap-panic (principal-construct? (get version d) (get hash-bytes d)))
57+
)
58+
)

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: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ genesis:
4848
plan:
4949
batches:
5050
- id: 0
51+
transactions:
52+
- emulated-contract-publish:
53+
contract-name: nft-trait
54+
emulated-sender: SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9
55+
path: "./.cache/requirements/SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.clar"
56+
clarity-version: 1
57+
epoch: "2.0"
58+
- id: 1
5159
transactions:
5260
- emulated-contract-publish:
5361
contract-name: aibtcdev-aibtc
@@ -65,16 +73,36 @@ plan:
6573
path: contracts/aibtcdev-resources-v1.clar
6674
clarity-version: 2
6775
epoch: "2.4"
68-
- id: 1
76+
- id: 2
6977
transactions:
78+
- emulated-contract-publish:
79+
contract-name: aibtcdev-airdrop-1
80+
emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
81+
path: contracts/aibtcdev-airdrop-1.clar
82+
clarity-version: 2
83+
- emulated-contract-publish:
84+
contract-name: aibtcdev-airdrop-2
85+
emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
86+
path: contracts/aibtcdev-airdrop-2.clar
87+
clarity-version: 2
7088
- emulated-contract-publish:
7189
contract-name: aibtcdev-bank-account
7290
emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
7391
path: contracts/aibtcdev-bank-account.clar
7492
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
7598
- emulated-contract-publish:
7699
contract-name: test-proxy
77100
emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
78101
path: contracts/test-proxy.clar
79102
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
80108
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+
});

0 commit comments

Comments
 (0)