Skip to content

Commit

Permalink
IPTs: save a storage slot for not storing the tokenizer explicitly
Browse files Browse the repository at this point in the history
tests IPTs control after IPNFT transfers

Signed-off-by: stadolf <[email protected]>
  • Loading branch information
elmariachi111 committed Jul 5, 2024
1 parent c4c15c0 commit b58a912
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 9 deletions.
7 changes: 2 additions & 5 deletions src/IPToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ error TokenCapped();
/**
* @title IPToken 1.3
* @author molecule.xyz
* @notice this is a template contract that's spawned by the Tokenizer
* @notice this is a template contract that's cloned by the Tokenizer
* @notice the owner of this contract is always the Tokenizer contract which enforces IPNFT holdership rules.
* The owner can increase the token supply as long as it's not explicitly capped.
* @dev formerly known as "molecules"
Expand All @@ -34,24 +34,21 @@ contract IPToken is ERC20BurnableUpgradeable, OwnableUpgradeable {

Metadata internal _metadata;

Tokenizer internal tokenizer;

function initialize(uint256 ipnftId, string calldata name, string calldata symbol, address originalOwner, string memory agreementCid)
external
initializer
{
__Ownable_init();
__ERC20_init(name, symbol);
_metadata = Metadata({ ipnftId: ipnftId, originalOwner: originalOwner, agreementCid: agreementCid });
tokenizer = Tokenizer(owner());
}

constructor() {
_disableInitializers();
}

modifier onlyTokenizerOrIPNFTHolder() {
if (_msgSender() != owner() && _msgSender() != tokenizer.ownerOf(_metadata.ipnftId)) {
if (_msgSender() != owner() && _msgSender() != Tokenizer(owner()).ownerOf(_metadata.ipnftId)) {
revert MustOwnIpnft();
}
_;
Expand Down
2 changes: 1 addition & 1 deletion src/Tokenizer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
/// @dev the permissioner checks if senders have agreed to legal requirements
IPermissioner public permissioner;

/// @notice the IPToken implementation this Tokenizer spawns
/// @notice the IPToken implementation this Tokenizer clones from
IPToken public ipTokenImplementation;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { IPermissioner, BlindPermissioner } from "../../src/Permissioner.sol";
// an error thrown by IPToken before 1.3
//error OnlyIssuerOrOwner();

contract TokenizerForkTest is Test {
contract Tokenizer13UpgradeForkTest is Test {
using SafeERC20Upgradeable for IPToken;

uint256 mainnetFork;
Expand Down
27 changes: 25 additions & 2 deletions test/Tokenizer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,18 @@ contract TokenizerTest is Test {
tokenContract.transfer(bob, 25_000);

tokenContract.issue(originalOwner, 50_000);
// this allows new owners of legacy IPTs to increase their supply:
tokenizer.issue(tokenContract, 50_000, originalOwner);

vm.startPrank(bob);
vm.expectRevert(MustOwnIpnft.selector);
tokenContract.issue(bob, 12345);
vm.expectRevert(MustOwnIpnft.selector);
tokenizer.issue(tokenContract, 12345, bob);

vm.expectRevert(MustOwnIpnft.selector);
tokenContract.cap();
vm.expectRevert(MustOwnIpnft.selector);
tokenizer.cap(tokenContract);

assertEq(tokenContract.balanceOf(alice), 25_000);
assertEq(tokenContract.balanceOf(bob), 25_000);
Expand All @@ -154,6 +157,26 @@ contract TokenizerTest is Test {
tokenizer.issue(tokenContract, 12345, bob);
}

function testIPNFTHolderControlsIPT() public {
vm.startPrank(originalOwner);
IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, "");
tokenContract.issue(bob, 50_000);
ipnft.transferFrom(originalOwner, alice, 1);

vm.startPrank(alice);
tokenContract.issue(alice, 50_000);
assertEq(tokenContract.balanceOf(alice), 50_000);

//the original owner *cannot* issue tokens anymore
//this actually worked before 1.3 since IPTs were bound to their original owner
vm.startPrank(originalOwner);
vm.expectRevert(MustOwnIpnft.selector);
tokenContract.issue(alice, 50_000);

vm.expectRevert(MustOwnIpnft.selector);
tokenizer.issue(tokenContract, 50_000, bob);
}

function testCanBeTokenizedOnlyOnce() public {
vm.startPrank(originalOwner);
tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, "");
Expand Down Expand Up @@ -189,7 +212,7 @@ contract TokenizerTest is Test {

assertEq(tokenContract.balanceOf(address(wallet)), 100_000);

//test the SAFE can send molecules to another account
//test the SAFE can send IPTs to another account
bytes memory transferCall = abi.encodeCall(tokenContract.transfer, (bob, 10_000));
bytes32 encodedTxDataHash = wallet.getTransactionHash(
address(tokenContract), 0, transferCall, Enum.Operation.Call, 80_000, 1 gwei, 20 gwei, address(0x0), payable(0x0), 0
Expand Down

0 comments on commit b58a912

Please sign in to comment.