Skip to content

Commit 56578df

Browse files
committed
(feat) (openai/gpt-5.5, reviewed F, tested T) route market creation through RIK launcher
1 parent 2977854 commit 56578df

6 files changed

Lines changed: 265 additions & 6 deletions

File tree

cli/src/index.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { exit } from "node:process";
55

66
import { Command } from "commander";
77
import type { Abi } from "viem";
8-
import { createPublicClient, createWalletClient, http, parseAbiItem, parseEther, toHex } from "viem";
8+
import { createPublicClient, createWalletClient, http, parseAbi, parseAbiItem, parseEther, toHex } from "viem";
99
import { privateKeyToAccount } from "viem/accounts";
1010
import { foundry, baseSepolia } from "viem/chains";
1111
import { DopplerSDK } from "@whetstone-research/doppler-sdk/evm";
@@ -39,6 +39,9 @@ const VERBOSE_ERRORS = process.env.VERBOSE_ERRORS ? true : fa
3939
const ENABLE_MARKET_COMMAND = process.env.ENABLE_MARKET_COMMAND ? true : false;
4040

4141
const abi = loadAbi();
42+
const RIKLauncherAbi = parseAbi([
43+
"function launch(uint256 repoId, (uint256 initialSupply, uint256 numTokensToSell, address numeraire, address tokenFactory, bytes tokenFactoryData, address governanceFactory, bytes governanceFactoryData, address poolInitializer, bytes poolInitializerData, address liquidityMigrator, bytes liquidityMigratorData, address integrator, bytes32 salt) p) returns (address asset)",
44+
]);
4245
const RepoRegisteredEvent = parseAbiItem(
4346
"event RepoRegistered(uint256 indexed repoId, address indexed registrant, uint64 githubOwnerId, uint64 registeredAt)"
4447
);
@@ -70,7 +73,9 @@ function addMarketCommand(program: Command): void {
7073

7174
marketCommand
7275
.command("new")
73-
.action(async (_) => {
76+
.requiredOption("--launcher <addr>", "deployed RIKLauncher address")
77+
.requiredOption("--repo-id <id>", "registered GitHub repository ID")
78+
.action(async (opts) => {
7479

7580
const { account, publicClient, walletClient } = clients();
7681
let doppler: DopplerSDK | null = null;
@@ -113,9 +118,19 @@ function addMarketCommand(program: Command): void {
113118
.withUserAddress(account.address)
114119
.build();
115120

116-
try {
117-
const { hookAddress, tokenAddress, poolId } = await doppler.factory.createDynamicAuction(params);
118-
console.log(`New marketplace created!\npair=$${symbol}/$WETH hookAddress=${hookAddress} tokenAddress=${tokenAddress} poolId=${poolId}`);
121+
try {
122+
const repoId = BigInt(opts.repoId);
123+
const factory = doppler.factory;
124+
const { createParams, hookAddress, tokenAddress } = await factory.encodeCreateDynamicAuctionParams(params);
125+
const hash = await walletClient.writeContract({
126+
address: opts.launcher as `0x${string}`,
127+
abi: RIKLauncherAbi,
128+
functionName: "launch",
129+
args: [repoId, createParams],
130+
account,
131+
});
132+
const r = await publicClient.waitForTransactionReceipt({ hash });
133+
console.log(`New marketplace created!\nrepo=${repoId} pair=$${symbol}/$WETH hookAddress=${hookAddress} tokenAddress=${tokenAddress} tx=${hash} status=${r.status}`);
119134
} catch (err) { die(err); }
120135
});
121136
}

contracts/src/RIKLauncher.sol

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// src/RIKLauncher.sol
2+
// SPDX-License-Identifier: Apache-2.0
3+
pragma solidity ^0.8.24;
4+
5+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
6+
7+
interface IAirlock {
8+
struct CreateParams {
9+
uint256 initialSupply;
10+
uint256 numTokensToSell;
11+
address numeraire;
12+
address tokenFactory;
13+
bytes tokenFactoryData;
14+
address governanceFactory;
15+
bytes governanceFactoryData;
16+
address poolInitializer;
17+
bytes poolInitializerData;
18+
address liquidityMigrator;
19+
bytes liquidityMigratorData;
20+
address integrator;
21+
bytes32 salt;
22+
}
23+
24+
function create(CreateParams calldata p)
25+
external
26+
returns (address asset, address pool, address governance, address timelock, address migrationPool);
27+
}
28+
29+
contract RIKLauncher {
30+
IAirlock public immutable airlock;
31+
IERC721 public immutable registry;
32+
33+
mapping(uint256 => address) public marketOf; // repoId -> asset
34+
mapping(address => uint256) public repoOf; // asset -> repoId
35+
36+
// events
37+
event MarketLaunched(uint256 indexed repoId, address indexed asset, address indexed launcher);
38+
39+
// errors
40+
error NotRikOwner(uint256 repoId, address caller);
41+
error AlreadyLaunched(uint256 repoId, address existing);
42+
43+
constructor(IAirlock _airlock, IERC721 _registry) {
44+
airlock = _airlock;
45+
registry = _registry;
46+
}
47+
48+
function launch(uint256 repoId, IAirlock.CreateParams calldata p) external returns (address asset) {
49+
// caller must be RIK.ownerOf() -> reverts otherwise
50+
if (registry.ownerOf(repoId) != msg.sender) revert NotRikOwner(repoId, msg.sender);
51+
52+
// only one market per repo can exist
53+
if (marketOf[repoId] != address(0)) revert AlreadyLaunched(repoId, marketOf[repoId]);
54+
55+
// send to doppler
56+
(asset,,,,) = airlock.create(p);
57+
marketOf[repoId] = asset;
58+
repoOf[asset] = repoId;
59+
60+
emit MarketLaunched(repoId, asset, msg.sender);
61+
}
62+
}

contracts/test/RIK.t.sol

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,14 @@ contract RIK_T is Test {
159159
uint64 github_owner_id = 999;
160160
Fixture memory f = _loadFixture("sample-jwt.json", repo_id, github_owner_id, address(this));
161161

162-
// register -> get repo struct -> check registrant
163162
_addKey(f);
163+
vm.warp(1_700_000_000);
164164
_register(f, repo_id, github_owner_id);
165+
165166
RIK.Repo memory r = rik.repoOf(repo_id);
167+
assertEq(r.githubRepoId, repo_id);
168+
assertEq(r.githubOwnerId, github_owner_id);
169+
assertEq(r.registeredAt, 1_700_000_000);
166170
assertEq(r.registrant, address(this));
167171
}
168172

@@ -228,6 +232,43 @@ contract RIK_T is Test {
228232
rik.register(f.kid, f.headerB64, f.payloadB64, f.signature, f.repoId + 1, f.ownerId);
229233
}
230234

235+
function test_RejectsWrongOwnerId() public {
236+
uint256 repo_id = 11112;
237+
uint64 github_owner_id = 999;
238+
Fixture memory f = _loadFixture("sample-jwt.json", repo_id, github_owner_id, address(this));
239+
240+
_addKey(f);
241+
242+
vm.prank(f.recipient);
243+
vm.expectRevert(abi.encodeWithSelector(JsonClaim.ClaimMismatch.selector, "repository_owner_id"));
244+
245+
rik.register(f.kid, f.headerB64, f.payloadB64, f.signature, f.repoId, f.ownerId + 1);
246+
}
247+
248+
function test_RejectsWrongIssuer() public {
249+
Fixture memory f = _loadFixture("wrong-issuer-jwt.json");
250+
251+
_addKey(f);
252+
253+
vm.prank(f.recipient);
254+
vm.expectRevert(abi.encodeWithSelector(JsonClaim.ClaimMismatch.selector, "iss"));
255+
256+
rik.register(f.kid, f.headerB64, f.payloadB64, f.signature, f.repoId, f.ownerId);
257+
}
258+
259+
function test_RejectsBadSignature() public {
260+
Fixture memory f = _loadFixture("sample-jwt.json");
261+
bytes memory badSignature = f.signature;
262+
badSignature[0] = bytes1(uint8(badSignature[0]) ^ 1);
263+
264+
_addKey(f);
265+
266+
vm.prank(f.recipient);
267+
vm.expectRevert(RIK.BadJwt.selector);
268+
269+
rik.register(f.kid, f.headerB64, f.payloadB64, badSignature, f.repoId, f.ownerId);
270+
}
271+
231272
function test_RejectsExpired() public {
232273
uint256 repo_id = 11112;
233274
uint64 github_owner_id = 999;
@@ -241,4 +282,16 @@ contract RIK_T is Test {
241282

242283
rik.register(f.kid, f.headerB64, f.payloadB64, f.signature, f.repoId, f.ownerId);
243284
}
285+
286+
function test_RejectsNotYetValid() public {
287+
Fixture memory f = _loadFixture("future-nbf-jwt.json");
288+
289+
_addKey(f);
290+
291+
vm.warp(f.nbf - 1);
292+
vm.prank(f.recipient);
293+
vm.expectRevert(bytes("token not yet valid"));
294+
295+
rik.register(f.kid, f.headerB64, f.payloadB64, f.signature, f.repoId, f.ownerId);
296+
}
244297
}

contracts/test/RIKLauncher.t.sol

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// test/RIKLauncher.t.sol
2+
// SPDX-License-Identifier: Apache-2.0
3+
pragma solidity ^0.8.24;
4+
5+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
6+
import {Test} from "forge-std/Test.sol";
7+
import {IAirlock, RIKLauncher} from "../src/RIKLauncher.sol";
8+
9+
contract MockAirlock is IAirlock {
10+
address public asset = address(0xA55E7);
11+
address public lastCaller;
12+
uint256 public lastInitialSupply;
13+
uint256 public lastNumTokensToSell;
14+
address public lastNumeraire;
15+
address public lastTokenFactory;
16+
bytes public lastTokenFactoryData;
17+
18+
function create(CreateParams calldata p)
19+
external
20+
returns (address asset_, address pool, address governance, address timelock, address migrationPool)
21+
{
22+
lastCaller = msg.sender;
23+
lastInitialSupply = p.initialSupply;
24+
lastNumTokensToSell = p.numTokensToSell;
25+
lastNumeraire = p.numeraire;
26+
lastTokenFactory = p.tokenFactory;
27+
lastTokenFactoryData = p.tokenFactoryData;
28+
29+
return (asset, address(0xB001), address(0xC0DE), address(0xD00D), address(0xE5C0));
30+
}
31+
}
32+
33+
contract MockRegistry {
34+
mapping(uint256 => address) public ownerOf;
35+
36+
function setOwner(uint256 repoId, address owner) external {
37+
ownerOf[repoId] = owner;
38+
}
39+
}
40+
41+
contract RIKLauncher_T is Test {
42+
MockAirlock airlock;
43+
MockRegistry registry;
44+
RIKLauncher launcher;
45+
46+
uint256 repoId = 11112;
47+
address owner = address(0xA11CE);
48+
49+
function setUp() public {
50+
airlock = new MockAirlock();
51+
registry = new MockRegistry();
52+
launcher = new RIKLauncher(airlock, IERC721(address(registry)));
53+
54+
registry.setOwner(repoId, owner);
55+
}
56+
57+
function test_LaunchRoutesThroughAirlockAndStoresMarket() public {
58+
IAirlock.CreateParams memory p = _params();
59+
60+
vm.expectEmit(true, true, true, true);
61+
emit RIKLauncher.MarketLaunched(repoId, airlock.asset(), owner);
62+
63+
vm.prank(owner);
64+
address asset = launcher.launch(repoId, p);
65+
66+
assertEq(asset, airlock.asset());
67+
assertEq(launcher.marketOf(repoId), asset);
68+
assertEq(launcher.repoOf(asset), repoId);
69+
assertEq(airlock.lastCaller(), address(launcher));
70+
assertEq(airlock.lastInitialSupply(), p.initialSupply);
71+
assertEq(airlock.lastNumTokensToSell(), p.numTokensToSell);
72+
assertEq(airlock.lastNumeraire(), p.numeraire);
73+
assertEq(airlock.lastTokenFactory(), p.tokenFactory);
74+
assertEq(airlock.lastTokenFactoryData(), p.tokenFactoryData);
75+
}
76+
77+
function test_RejectsNonRikOwner() public {
78+
address bob = address(0xB0B);
79+
80+
vm.prank(bob);
81+
vm.expectRevert(abi.encodeWithSelector(RIKLauncher.NotRikOwner.selector, repoId, bob));
82+
83+
launcher.launch(repoId, _params());
84+
}
85+
86+
function test_RejectsDuplicateLaunch() public {
87+
vm.prank(owner);
88+
address asset = launcher.launch(repoId, _params());
89+
90+
vm.prank(owner);
91+
vm.expectRevert(abi.encodeWithSelector(RIKLauncher.AlreadyLaunched.selector, repoId, asset));
92+
93+
launcher.launch(repoId, _params());
94+
}
95+
96+
function _params() internal pure returns (IAirlock.CreateParams memory p) {
97+
p.initialSupply = 1_000_000 ether;
98+
p.numTokensToSell = 500_000 ether;
99+
p.numeraire = address(0x4200000000000000000000000000000000000006);
100+
p.tokenFactory = address(0x70E3);
101+
p.tokenFactoryData = hex"1234";
102+
p.governanceFactory = address(0x90C0);
103+
p.governanceFactoryData = hex"5678";
104+
p.poolInitializer = address(0xB001);
105+
p.poolInitializerData = hex"abcd";
106+
p.liquidityMigrator = address(0x1111);
107+
p.liquidityMigratorData = hex"dcba";
108+
p.integrator = address(0x2222);
109+
p.salt = bytes32(uint256(1));
110+
}
111+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"kidText": "kid-001",
3+
"iss": "https://token.actions.githubusercontent.com",
4+
"recipient": "0x00000000000000000000000000000000000A11CE",
5+
"repoId": 11112,
6+
"ownerId": 999,
7+
"exp": 4102444800,
8+
"nbf": 1700000000
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"kidText": "kid-001",
3+
"iss": "https://example.com",
4+
"recipient": "0x00000000000000000000000000000000000A11CE",
5+
"repoId": 11112,
6+
"ownerId": 999,
7+
"exp": 4102444800,
8+
"nbf": 0
9+
}

0 commit comments

Comments
 (0)