Skip to content

Commit 88feb64

Browse files
committed
refactor: remove state changing methods from wrappedipt
1 parent 2fec1e1 commit 88feb64

File tree

3 files changed

+76
-22
lines changed

3 files changed

+76
-22
lines changed

src/WrappedIPToken.sol

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/I
99
/**
1010
* @title WrappedIPToken
1111
* @author molecule.xyz
12-
* @notice this is a template contract that's cloned by the Tokenizer
13-
* @notice this contract is used to wrap an ERC20 token and extend its metadata
12+
* @notice A read-only wrapper that extends existing ERC20 tokens with IP metadata
13+
* @dev This contract provides IP metadata for an existing ERC20 token without proxying
14+
* state-changing operations. It only implements IIPToken interface functions and
15+
* read-only ERC20 view functions. Users must interact with the underlying token
16+
* directly for transfers, approvals, and other state changes.
1417
*/
1518
contract WrappedIPToken is IIPToken, Initializable {
1619
IERC20Metadata public wrappedToken;
@@ -62,32 +65,16 @@ contract WrappedIPToken is IIPToken, Initializable {
6265
return wrappedToken.balanceOf(account);
6366
}
6467

65-
function transfer(address to, uint256 amount) public returns (bool) {
66-
return wrappedToken.transfer(to, amount);
67-
}
68-
69-
function allowance(address owner, address spender) public view returns (uint256) {
70-
return wrappedToken.allowance(owner, spender);
71-
}
72-
73-
function approve(address spender, uint256 amount) public returns (bool) {
74-
return wrappedToken.approve(spender, amount);
75-
}
76-
77-
function transferFrom(address from, address to, uint256 amount) public returns (bool) {
78-
return wrappedToken.transferFrom(from, to, amount);
79-
}
80-
8168
function totalIssued() public view override returns (uint256) {
8269
return wrappedToken.totalSupply();
8370
}
8471

8572
function issue(address, uint256) public virtual override {
86-
revert("WrappedIPToken: cannot issue");
73+
revert("WrappedIPToken: read-only wrapper - use underlying token for minting");
8774
}
8875

8976
function cap() public virtual override {
90-
revert("WrappedIPToken: cannot cap");
77+
revert("WrappedIPToken: read-only wrapper - use underlying token for cappping");
9178
}
9279

9380
function uri() external view override returns (string memory) {

test/Forking/Tokenizer14UpgradeForkTest.t.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,9 @@ contract Tokenizer14UpgradeForkTest is Test {
414414
vm.expectRevert(); // WrappedIPToken should not allow cap operations
415415
WrappedIPToken(address(wrappedToken)).cap();
416416

417+
// Test that WrappedIPToken only implements IIPToken state-changing operations (which revert)
418+
// ERC20 state-changing functions are no longer implemented
419+
417420
// Test that we cannot attach another token to the same IPNFT
418421
FakeERC20 anotherToken = new FakeERC20("Another Token", "ANT");
419422
vm.expectRevert(AlreadyTokenized.selector);

test/TokenizerWrapped.t.sol

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,77 @@ contract TokenizerWrappedTest is Test {
144144
IIPToken tokenContract = tokenizer.attachIpt(1, agreementCid, "", erc20);
145145

146146
// Wrapped tokens should not be able to issue or cap
147-
vm.expectRevert("WrappedIPToken: cannot issue");
147+
vm.expectRevert("WrappedIPToken: read-only wrapper - use underlying token for minting");
148148
tokenContract.issue(alice, 1000);
149149

150-
vm.expectRevert("WrappedIPToken: cannot cap");
150+
vm.expectRevert("WrappedIPToken: read-only wrapper - use underlying token for cappping");
151151
tokenContract.cap();
152152
}
153153

154+
function testWrappedTokenStateChangingOperationsRevert() public {
155+
vm.startPrank(originalOwner);
156+
erc20 = new FakeERC20("TestToken", "TEST");
157+
erc20.mint(originalOwner, 1_000_000 ether);
158+
159+
IIPToken tokenContract = tokenizer.attachIpt(1, agreementCid, "", erc20);
160+
161+
// Test that IIPToken state-changing operations revert with proper messages
162+
vm.expectRevert("WrappedIPToken: read-only wrapper - use underlying token for minting");
163+
tokenContract.issue(alice, 1000);
164+
165+
vm.expectRevert("WrappedIPToken: read-only wrapper - use underlying token for cappping");
166+
tokenContract.cap();
167+
}
168+
169+
function testWrappedTokenReadOnlyOperationsWork() public {
170+
vm.startPrank(originalOwner);
171+
erc20 = new FakeERC20("TestToken", "TEST");
172+
erc20.mint(originalOwner, 1_000_000 ether);
173+
174+
IIPToken tokenContract = tokenizer.attachIpt(1, agreementCid, "", erc20);
175+
WrappedIPToken wrappedToken = WrappedIPToken(address(tokenContract));
176+
177+
// Test that read-only operations still work
178+
assertEq(wrappedToken.balanceOf(originalOwner), 1_000_000 ether);
179+
assertEq(wrappedToken.totalSupply(), 1_000_000 ether);
180+
assertEq(wrappedToken.name(), "TestToken");
181+
assertEq(wrappedToken.symbol(), "TEST");
182+
assertEq(wrappedToken.decimals(), 18);
183+
assertEq(wrappedToken.totalIssued(), 1_000_000 ether);
184+
185+
// Test IP metadata functions work
186+
assertEq(wrappedToken.metadata().ipnftId, 1);
187+
assertEq(wrappedToken.metadata().originalOwner, originalOwner);
188+
assertEq(wrappedToken.metadata().agreementCid, agreementCid);
189+
190+
// Test URI function works
191+
string memory uri = wrappedToken.uri();
192+
assertTrue(bytes(uri).length > 0);
193+
}
194+
195+
function testUnderlyingTokenOperationsStillWork() public {
196+
vm.startPrank(originalOwner);
197+
erc20 = new FakeERC20("TestToken", "TEST");
198+
erc20.mint(originalOwner, 1_000_000 ether);
199+
200+
IIPToken tokenContract = tokenizer.attachIpt(1, agreementCid, "", erc20);
201+
202+
// Users can still use the underlying token directly for transfers
203+
assertTrue(erc20.transfer(alice, 100_000 ether));
204+
assertEq(erc20.balanceOf(alice), 100_000 ether);
205+
assertEq(erc20.balanceOf(originalOwner), 900_000 ether);
206+
207+
// Approvals work on the underlying token
208+
assertTrue(erc20.approve(alice, 50_000 ether));
209+
assertEq(erc20.allowance(originalOwner, alice), 50_000 ether);
210+
211+
// Transfer from works on the underlying token
212+
vm.startPrank(alice);
213+
assertTrue(erc20.transferFrom(originalOwner, alice, 25_000 ether));
214+
assertEq(erc20.balanceOf(alice), 125_000 ether);
215+
assertEq(erc20.balanceOf(originalOwner), 875_000 ether);
216+
}
217+
154218
// Helper function to check if a string contains a substring
155219
function contains(string memory source, string memory search) internal pure returns (bool) {
156220
bytes memory sourceBytes = bytes(source);

0 commit comments

Comments
 (0)