Skip to content

Commit 7f4b11f

Browse files
committed
Add wrapped_token_registry and tests
1 parent 38014a9 commit 7f4b11f

7 files changed

Lines changed: 181 additions & 36 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import path from "node:path";
2+
import { fileURLToPath } from "node:url";
3+
4+
import * as AleoUtils from "../lib/aleo-test-utils.js";
5+
6+
export const PROGRAM_ID = "wrapped_token_registry.aleo";
7+
export const WRAPPED_TOKEN_ID = "99999field";
8+
9+
const __filename = fileURLToPath(import.meta.url);
10+
const __dirname = path.dirname(__filename);
11+
12+
export const PROGRAM_PATH = path.join(__dirname, "..", "..", "wrapped_token_registry");
13+
14+
export async function transferPublic(account, recipient, amountU128) {
15+
const privateKey = account.privateKey().to_string();
16+
return await AleoUtils.leoExecute(PROGRAM_PATH, "transfer_public", [recipient, amountU128], {
17+
privateKey,
18+
});
19+
}

arc-0020-2026/tests/lib/aleo-test-utils.js

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ export async function startDevnode(opts = {}) {
175175
const leoDevnodeBin = opts.leoDevnodeBin || LEO_BIN;
176176
const privateKey = opts.privateKey || DEFAULT_PRIVATE_KEYS[0];
177177

178-
const logPath = opts.logPath || path.join(__dirname, "..", "snarkos-devnet.log");
178+
const logsDir = path.join(__dirname, "..", "logs");
179+
fs.mkdirSync(logsDir, { recursive: true });
180+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
181+
const logPath = opts.logPath || path.join(logsDir, `snarkos-devnet-${timestamp}.log`);
179182
const storageRoot =
180183
opts.storageRoot || path.join(__dirname, "..", ".snarkos-devnet");
181184

@@ -308,26 +311,29 @@ export async function leoBuild(programPath, opts = {}) {
308311
export async function leoDeploy(programPath, opts = {}) {
309312
const privateKey = opts.privateKey || DEFAULT_PRIVATE_KEYS[0];
310313

311-
return await run(
312-
LEO_BIN,
313-
[
314-
"deploy",
315-
"--network",
316-
"testnet",
317-
"--endpoint",
318-
NETWORK_URL,
319-
"--private-key",
320-
privateKey,
321-
"--broadcast",
322-
"--yes",
323-
"--devnet",
324-
"--max-wait",
325-
String(opts.maxWait ?? 15),
326-
"--blocks-to-check",
327-
String(opts.blocksToCheck ?? 15),
328-
],
329-
{ cwd: programPath, label: "leo deploy" },
330-
);
314+
const args = [
315+
"deploy",
316+
"--network",
317+
"testnet",
318+
"--endpoint",
319+
NETWORK_URL,
320+
"--private-key",
321+
privateKey,
322+
"--broadcast",
323+
"--yes",
324+
"--devnet",
325+
"--max-wait",
326+
String(opts.maxWait ?? 15),
327+
"--blocks-to-check",
328+
String(opts.blocksToCheck ?? 15),
329+
];
330+
if (opts.skip?.length) {
331+
for (const s of opts.skip) {
332+
args.push("--skip", s);
333+
}
334+
}
335+
336+
return await run(LEO_BIN, args, { cwd: programPath, label: "leo deploy" });
331337
}
332338

333339
export async function leoExecute(programPath, fnName, inputs, opts = {}) {
@@ -384,33 +390,38 @@ export async function leoMappingValue(programName, mappingName, key, opts = {})
384390

385391
export async function leoProgramExists(programName, opts = {}) {
386392
try {
387-
await run(
388-
LEO_BIN,
389-
[
390-
"query",
391-
"program",
392-
programName,
393-
"--network",
394-
"testnet",
395-
"--endpoint",
396-
NETWORK_URL,
397-
],
398-
{ label: "leo query program" },
399-
);
393+
const args = [
394+
"query",
395+
"program",
396+
programName,
397+
"--network",
398+
"testnet",
399+
"--endpoint",
400+
NETWORK_URL,
401+
];
402+
const isLocalEndpoint =
403+
NETWORK_URL.includes("127.0.0.1") || NETWORK_URL.includes("localhost");
404+
if (opts.devnet !== false && isLocalEndpoint) {
405+
args.push("--devnet");
406+
}
407+
await run(LEO_BIN, args, { label: "leo query program" });
400408
return true;
401409
} catch {
402410
return false;
403411
}
404412
}
405413

406414
export async function deployProgramFromFile(opts) {
407-
const { programId, programPath } = opts;
415+
const { programId, programPath, skip } = opts;
408416
if (!programId) throw new Error("deployProgramFromFile requires programId");
409417
if (!programPath) throw new Error("deployProgramFromFile requires programPath");
410418

411419
await leoBuild(programPath, opts);
412420
try {
413-
await leoDeploy(programPath, { privateKey: DEFAULT_PRIVATE_KEYS[0] });
421+
await leoDeploy(programPath, {
422+
privateKey: DEFAULT_PRIVATE_KEYS[0],
423+
skip,
424+
});
414425
} catch (e) {
415426
const msg = String(e?.message || e);
416427
if (!msg.includes("already exists on the network")) throw e;

arc-0020-2026/tests/token-registry.test.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@ import { fileURLToPath } from "node:url";
33

44
import * as AleoUtils from "./lib/aleo-test-utils.js";
55
import * as TokenRegistry from "./contracts/token-registry.js";
6+
import * as WrappedTokenRegistry from "./contracts/wrapped-token-registry.js";
67

78
const __filename = fileURLToPath(import.meta.url);
89
const __dirname = path.dirname(__filename);
910

1011
describe("token_registry.aleo", () => {
1112
const programPath = path.join(__dirname, "..", "token_registry");
13+
const wrappedProgramPath = path.join(__dirname, "..", "wrapped_token_registry");
1214
const pk0 = AleoUtils.DEFAULT_PRIVATE_KEYS[0];
1315
const addr0 = AleoUtils.addresses[0];
1416
const addr1 = AleoUtils.addresses[1];
1517

1618
// Custom token ID (must not equal CREDITS_RESERVED_TOKEN_ID)
1719
const CUSTOM_TOKEN_ID = "12345field";
20+
// Token ID wrapped by wrapped_token_registry.aleo
21+
const WRAPPED_TOKEN_ID = WrappedTokenRegistry.WRAPPED_TOKEN_ID;
1822
const MAX_SUPPLY = "1000000u128";
1923
const MINT_AMOUNT = "1000u128";
2024
const TRANSFER_AMOUNT = "200u128";
@@ -28,6 +32,8 @@ describe("token_registry.aleo", () => {
2832
await expect(p).rejects.toThrow(/Transaction rejected|failed \(code/i);
2933
}
3034

35+
let wrappedTokenRegistryDeployed = false;
36+
3137
beforeAll(async () => {
3238
try {
3339
await AleoUtils.startDevnode();
@@ -36,6 +42,20 @@ describe("token_registry.aleo", () => {
3642
programId: TokenRegistry.PROGRAM_ID,
3743
programPath,
3844
});
45+
46+
try {
47+
const prevEndpoint = process.env.ENDPOINT;
48+
process.env.ENDPOINT = AleoUtils.NETWORK_URL;
49+
await AleoUtils.deployProgramFromFile({
50+
programId: WrappedTokenRegistry.PROGRAM_ID,
51+
programPath: wrappedProgramPath,
52+
});
53+
process.env.ENDPOINT = prevEndpoint;
54+
wrappedTokenRegistryDeployed = true;
55+
} catch (e) {
56+
process.env.ENDPOINT = undefined;
57+
wrappedTokenRegistryDeployed = false;
58+
}
3959
} catch (e) {
4060
await AleoUtils.stopDevnode();
4161
throw e;
@@ -185,4 +205,53 @@ describe("token_registry.aleo", () => {
185205
),
186206
);
187207
});
208+
209+
test("wrapped_token_registry: transfer via TransferPublic interface", async () => {
210+
if (!wrappedTokenRegistryDeployed) {
211+
console.warn(
212+
"Skipping: wrapped_token_registry not deployed (dependency deploy skips both programs)",
213+
);
214+
return;
215+
}
216+
// Register and mint the token wrapped by wrapped_token_registry (99999field)
217+
const name = "1413829460u128";
218+
const symbol = "1413829460u128";
219+
const decimals = "6u8";
220+
const extAuthRequired = "false";
221+
const extAuthParty = addr0;
222+
223+
const registerExec = await AleoUtils.leoExecute(
224+
programPath,
225+
"register_token",
226+
[WRAPPED_TOKEN_ID, name, symbol, decimals, MAX_SUPPLY, extAuthRequired, extAuthParty],
227+
{ privateKey: pk0 },
228+
);
229+
await expectConfirmed(registerExec);
230+
231+
const mintExec = await TokenRegistry.mintPublic(
232+
AleoUtils.accounts[0],
233+
WRAPPED_TOKEN_ID,
234+
addr0,
235+
"500u128",
236+
AUTHORIZED_UNTIL,
237+
);
238+
await expectConfirmed(mintExec);
239+
240+
// Transfer using wrapped_token_registry (TransferPublic interface)
241+
const transferExec = await WrappedTokenRegistry.transferPublic(
242+
AleoUtils.accounts[0],
243+
addr1,
244+
"150u128",
245+
);
246+
await expectConfirmed(transferExec);
247+
248+
// Verify: addr1 received and can transfer back via token_registry
249+
const execBack = await TokenRegistry.transferPublic(
250+
AleoUtils.accounts[1],
251+
WRAPPED_TOKEN_ID,
252+
addr0,
253+
"50u128",
254+
);
255+
await expectConfirmed(execBack);
256+
});
188257
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.env
2+
*.avm
3+
*.prover
4+
*.verifier
5+
outputs/
6+
*.log
7+
build
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# wrapped_token_registry.aleo
2+
3+
Stateless wrapper around `token_registry.aleo` that implements the `TransferPublic` interface (same as `wrapped_credits.aleo`).
4+
5+
## Purpose
6+
7+
Provides a uniform `transfer_public(public recipient: address, public amount: u128) -> Final` interface for the credits-reserved token in the token registry. This allows programs that expect the `TransferPublic` interface to work with either `wrapped_credits.aleo` or `wrapped_token_registry.aleo` interchangeably.
8+
9+
## Dependencies
10+
11+
- `token_registry.aleo` (local, `../token_registry`)
12+
13+
## Token
14+
15+
Wraps the `CREDITS_RESERVED_TOKEN_ID` token in the token registry. Balances are stored in `token_registry.aleo`; this program holds no state and merely forwards calls.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"program":"wrapped_token_registry.aleo","version":"0.1.0","description":"Stateless wrapper around token_registry implementing TransferPublic","license":"MIT","leo":"3.4.0","dependencies":[{"name":"token_registry.aleo","location":"network","path":null,"edition":null}],"dev_dependencies":null}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import token_registry.aleo;
2+
3+
// Interface matching compliant_token_template.aleo transfer_public signature (PR 29151)
4+
interface TransferPublic {
5+
fn transfer_public(public recipient: address, public amount: u128) -> Final;
6+
}
7+
8+
// Custom token ID wrapped by this program (distinct from CREDITS_RESERVED_TOKEN_ID)
9+
const WRAPPED_TOKEN_ID: field = 99999field;
10+
11+
program wrapped_token_registry.aleo : TransferPublic {
12+
@noupgrade
13+
constructor() {}
14+
15+
fn transfer_public(public recipient: address, public amount: u128) -> Final {
16+
// Use transfer_public_as_signer so the sender is the transaction signer (user),
17+
// not this program's address (self.caller would be wrapped_token_registry).
18+
let tr_final = token_registry.aleo/transfer_public_as_signer(WRAPPED_TOKEN_ID, recipient, amount);
19+
return final {
20+
tr_final.run();
21+
};
22+
}
23+
}

0 commit comments

Comments
 (0)