Skip to content

Commit 9362140

Browse files
Merge pull request #149 from moleculeprotocol/feature/eip-1167
Revisit EIP1167 for IPNFT Tokenization
2 parents fd666e9 + 4a77522 commit 9362140

File tree

17 files changed

+434
-79
lines changed

17 files changed

+434
-79
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
RPC_URL="http://127.0.0.1:8545"
2+
MAINNET_RPC_URL=
23
#RPC_URL="https://goerli.infura.io/v3/<your infura key>"
34
INUFRA_KEY=
45
ETHERSCAN_API_KEY=

.github/workflows/test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ jobs:
3434
id: build
3535

3636
- name: Run Forge tests
37+
env:
38+
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
3739
run: |
3840
forge test -vvv
3941
id: test
4042

4143
- name: Run Hardhat tests
42-
run: yarn hardhat test --network hardhat
44+
run: yarn test
4345

4446
# - name: Run Slither
4547
# uses: crytic/[email protected]

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ IP-NFTs allow their users to tokenize intellectual property. This repo contains
1818

1919
- Subgraph: <https://api.thegraph.com/subgraphs/name/moleculeprotocol/ip-nft-mainnet>
2020

21-
tokenizer implementation 2: 0x9C70FA8c87D7e94Fd63eeCCcA657D5c4224a36f3
21+
tokenizer implementation 1.2: 0xE8701330F196FeFe415b28dAA767AB076F42557A
22+
tokenizer implementation 1.1: 0x9C70FA8c87D7e94Fd63eeCCcA657D5c4224a36f3
23+
iptoken implementation: 0x9E4fc6E6d1A64e3429aB852d3CB31AD7aa06997A
2224
ipnft implementation 2.4: 0x6B179Dffac5E190c670176606f552cB792847f80
2325

2426
Defender Relayer that signs off minting requests from our side:
@@ -37,11 +39,11 @@ Defender Relayer that signs off minting requests from our side:
3739
| Crowdsale | [0x8c83DA72b4591bE526ca8C7cb848bC89c0e23373](https://goerli.etherscan.io/address/0x8c83DA72b4591bE526ca8C7cb848bC89c0e23373#code>) | <a href="https://thirdweb.com/goerli/0x8c83DA72b4591bE526ca8C7cb848bC89c0e23373?utm_source=contract_badge" target="_blank"><img width="200" height="45" src="https://badges.thirdweb.com/contract?address=0x8c83DA72b4591bE526ca8C7cb848bC89c0e23373&theme=dark&chainId=5" alt="View contract" /></a> |
3840
| StakedLockingCrowdSale | [0x46c3369dece07176ad7164906d3593aa4c126d35](https://goerli.etherscan.io/address/0x46c3369dece07176ad7164906d3593aa4c126d35#code) | <a href="https://thirdweb.com/goerli/0x46c3369dece07176ad7164906d3593aa4c126d35?utm_source=contract_badge" target="_blank"><img width="200" height="45" src="https://badges.thirdweb.com/contract?address=0x46c3369dece07176ad7164906d3593aa4c126d35&theme=dark&chainId=5" alt="View contract" /></a> |
3941
| SignedMintAuthorizer | [0x5e555eE24DB66825171Ac63EA614864987CEf1Af](https://goerli.etherscan.io/address/0x5e555eE24DB66825171Ac63EA614864987CEf1Af#code) | <a href="https://thirdweb.com/goerli/0x5e555eE24DB66825171Ac63EA614864987CEf1Af?utm_source=contract_badge" target="_blank"><img width="200" height="45" src="https://badges.thirdweb.com/contract?address=0x5e555eE24DB66825171Ac63EA614864987CEf1Af&theme=dark&chainId=5" alt="View contract" /></a> |
42+
| IPToken Implementation | [0x38Ca0fEEc7cd48629f9388727bfA747859a6feE7](https://goerli.etherscan.io/address/0x38Ca0fEEc7cd48629f9388727bfA747859a6feE7#code) | <a href="https://thirdweb.com/goerli/0x38Ca0fEEc7cd48629f9388727bfA747859a6feE7?utm_source=contract_badge" target="_blank"><img width="200" height="45" src="https://badges.thirdweb.com/contract?address=0x38Ca0fEEc7cd48629f9388727bfA747859a6feE7&theme=dark&chainId=5" alt="View contract" /></a> |
4043

41-
- Subgraph: https://api.thegraph.com/subgraphs/name/moleculeprotocol/ip-nft-goerli>
44+
- Subgraph: https://api.thegraph.com/subgraphs/name/moleculeprotocol/ip-nft-goerli
4245

43-
- Tokenizer: 0xb12494eeA6B992d0A1Db3C5423BE7a2d2337F58c (formerly known as Synthesizer)
44-
Implementation: 0x64580CC2c28aaEd31B2DF9a154dA9620C690BDec (reinit 4)
46+
- Tokenizer Implementation 1.2: 0x18E5ae026CFC8020b2eDbA7050eA6144Fd313c02 (reinit 4) <https://goerli.etherscan.io/address/0x18E5ae026CFC8020b2eDbA7050eA6144Fd313c02#code>
4547

4648
- Bio pricefeed: 0x8647dEFdEAAdF5448d021B364B2F17815aba4360
4749
<https://goerli.etherscan.io/address/0x8647defdeaadf5448d021b364b2f17815aba4360#code>

foundry.toml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,25 @@ src = 'src'
33
out = 'out'
44
libs = ['lib']
55
test = 'test'
6-
cache_path = 'cache_forge'
6+
cache_path = 'cache_forge'
77
solc_version = "0.8.18"
8-
gas_reports = ["IPNFT", "IPNFTV2", "SchmackoSwap", "Tokenizer", "IPToken", "CrowdSale", "LockingCrowdSale", "StakedLockingCrowdSale", "TimelockedToken", "TermsAcceptedPermissioner", "SignedMintAuthorizer"]
9-
fs_permissions = [{ access = "read-write", path = "./SALEID.txt"}]
8+
gas_reports = [
9+
"IPNFT",
10+
"IPNFTV2",
11+
"SchmackoSwap",
12+
"Tokenizer",
13+
"IPToken",
14+
"CrowdSale",
15+
"LockingCrowdSale",
16+
"StakedLockingCrowdSale",
17+
"TimelockedToken",
18+
"TermsAcceptedPermissioner",
19+
"SignedMintAuthorizer",
20+
]
21+
fs_permissions = [
22+
{ access = "read-write", path = "./SALEID.txt" },
23+
{ access = "read", path = "./out" },
24+
]
1025

1126
[fmt]
1227
bracket_spacing = true
@@ -18,5 +33,5 @@ override_spacing = false
1833

1934
[rpc_endpoints]
2035
optimism = "https://optimism-goerli.infura.io/v3/${INFURA_KEY}"
21-
goerli = "https://goerli.infura.io/v3/${INFURA_KEY}"
22-
mainnet = "https://mainnet.infura.io/v3/${INFURA_KEY}"
36+
goerli = "https://goerli.infura.io/v3/${INFURA_KEY}"
37+
mainnet = "https://mainnet.infura.io/v3/${INFURA_KEY}"

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"license": "MIT",
3-
"scripts": {},
3+
"scripts": {
4+
"test": "hardhat test --network hardhat"
5+
},
46
"devDependencies": {
57
"@nomicfoundation/hardhat-foundry": "^1.0.0",
68
"@nomicfoundation/hardhat-network-helpers": "^1.0.6",

script/IPToken.s.sol

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.18;
4+
5+
import "forge-std/Script.sol";
6+
import "../src/IPToken.sol";
7+
8+
contract DeployIPTokenImpl is Script {
9+
function run() external {
10+
vm.startBroadcast();
11+
IPToken iptoken = new IPToken();
12+
vm.stopBroadcast();
13+
14+
console.log("new ip token implementation at %s", address(iptoken));
15+
}
16+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.18;
3+
4+
import "forge-std/Script.sol";
5+
import { Tokenizer } from "../../src/Tokenizer.sol";
6+
import { IPToken } from "../../src/IPToken.sol";
7+
import { console } from "forge-std/console.sol";
8+
9+
contract RolloutTokenizerV12 is Script {
10+
function run() public {
11+
vm.startBroadcast();
12+
13+
IPToken newIpTokenImplementation = new IPToken();
14+
Tokenizer newTokenizerImplementation = new Tokenizer();
15+
16+
bytes memory upgradeCallData = abi.encodeWithSelector(Tokenizer.setIPTokenImplementation.selector, address(newIpTokenImplementation));
17+
18+
console.log("NEWTOKENIMPLEMENTATION=%s", address(newIpTokenImplementation));
19+
console.log("NEWTOKENIZER=%s", address(newTokenizerImplementation));
20+
console.logBytes(upgradeCallData);
21+
22+
vm.stopBroadcast();
23+
}
24+
}

src/IPToken.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ contract IPToken is ERC20BurnableUpgradeable, OwnableUpgradeable {
4040
_metadata = metadata_;
4141
}
4242

43+
constructor() {
44+
_disableInitializers();
45+
}
46+
4347
modifier onlyIssuerOrOwner() {
4448
if (_msgSender() != _metadata.originalOwner && _msgSender() != owner()) {
4549
revert OnlyIssuerOrOwner();

src/Tokenizer.sol

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import { IPNFT } from "./IPNFT.sol";
1111

1212
error MustOwnIpnft();
1313
error AlreadyTokenized();
14+
error ZeroAddress();
1415

15-
/// @title Tokenizer 1.1
16+
/// @title Tokenizer 1.2
1617
/// @author molecule.to
17-
/// @notice tokenizes an IPNFT to an ERC20 token (called IPT) and controls its supply.
18+
/// @notice tokenizes an IPNFT to an ERC20 token (called IPToken or IPT) and controls its supply.
1819
contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
1920
event TokensCreated(
2021
uint256 indexed moleculesId,
@@ -27,15 +28,23 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
2728
string symbol
2829
);
2930

31+
event IPTokenImplementationUpdated(IPToken indexed old, IPToken indexed _new);
32+
event PermissionerUpdated(IPermissioner indexed old, IPermissioner indexed _new);
33+
3034
IPNFT internal ipnft;
3135

32-
//this is the old term to keep the storage layout intact
36+
/// @dev a map of all IPTs. We're staying with the the initial term "synthesized" to keep the storage layout intact
3337
mapping(uint256 => IPToken) public synthesized;
38+
39+
/// @dev not used, needed to ensure that storage slots are still in order after 1.1 -> 1.2, use ipTokenImplementation
3440
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
3541
address immutable tokenImplementation;
3642

3743
/// @dev the permissioner checks if senders have agreed to legal requirements
38-
IPermissioner permissioner;
44+
IPermissioner public permissioner;
45+
46+
/// @notice the IPToken implementation this Tokenizer spawns
47+
IPToken public ipTokenImplementation;
3948

4049
/**
4150
* @param _ipnft the IPNFT contract
@@ -50,12 +59,28 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
5059

5160
/// @custom:oz-upgrades-unsafe-allow constructor
5261
constructor() {
53-
tokenImplementation = address(new IPToken());
62+
tokenImplementation = address(0);
5463
_disableInitializers();
5564
}
5665

5766
/**
58-
* @dev called after an upgrade to reinitialize a new permissioner impl. This is 4 for görli compatibility
67+
* @notice sets the new implementation address of the IPToken
68+
* @param _ipTokenImplementation address pointing to the new implementation
69+
*/
70+
function setIPTokenImplementation(IPToken _ipTokenImplementation) external onlyOwner {
71+
/*
72+
could call some functions on old contract to make sure its tokenizer not another contract behind a proxy for safety
73+
*/
74+
if (address(_ipTokenImplementation) == address(0)) {
75+
revert ZeroAddress();
76+
}
77+
78+
emit IPTokenImplementationUpdated(ipTokenImplementation, _ipTokenImplementation);
79+
ipTokenImplementation = _ipTokenImplementation;
80+
}
81+
82+
/**
83+
* @dev called after an upgrade to reinitialize a new permissioner impl.
5984
* @param _permissioner the new TermsPermissioner
6085
*/
6186
function reinit(IPermissioner _permissioner) public onlyOwner reinitializer(4) {
@@ -84,7 +109,7 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
84109
}
85110

86111
// https://github.com/OpenZeppelin/workshops/tree/master/02-contracts-clone
87-
token = IPToken(Clones.clone(tokenImplementation));
112+
token = IPToken(Clones.clone(address(ipTokenImplementation)));
88113
string memory name = string.concat("IP Tokens of IPNFT #", Strings.toString(ipnftId));
89114
token.initialize(name, tokenSymbol, TokenMetadata(ipnftId, _msgSender(), agreementCid));
90115

0 commit comments

Comments
 (0)