Skip to content

Commit

Permalink
Merge pull request #149 from moleculeprotocol/feature/eip-1167
Browse files Browse the repository at this point in the history
Revisit EIP1167 for IPNFT Tokenization
  • Loading branch information
elmariachi111 authored Jan 17, 2024
2 parents fd666e9 + 4a77522 commit 9362140
Show file tree
Hide file tree
Showing 17 changed files with 434 additions and 79 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
RPC_URL="http://127.0.0.1:8545"
MAINNET_RPC_URL=
#RPC_URL="https://goerli.infura.io/v3/<your infura key>"
INUFRA_KEY=
ETHERSCAN_API_KEY=
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ jobs:
id: build

- name: Run Forge tests
env:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
run: |
forge test -vvv
id: test

- name: Run Hardhat tests
run: yarn hardhat test --network hardhat
run: yarn test

# - name: Run Slither
# uses: crytic/slither-action@v0.2.0
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ IP-NFTs allow their users to tokenize intellectual property. This repo contains

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

tokenizer implementation 2: 0x9C70FA8c87D7e94Fd63eeCCcA657D5c4224a36f3
tokenizer implementation 1.2: 0xE8701330F196FeFe415b28dAA767AB076F42557A
tokenizer implementation 1.1: 0x9C70FA8c87D7e94Fd63eeCCcA657D5c4224a36f3
iptoken implementation: 0x9E4fc6E6d1A64e3429aB852d3CB31AD7aa06997A
ipnft implementation 2.4: 0x6B179Dffac5E190c670176606f552cB792847f80

Defender Relayer that signs off minting requests from our side:
Expand All @@ -37,11 +39,11 @@ Defender Relayer that signs off minting requests from our side:
| 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> |
| 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> |
| 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> |
| 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> |

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

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

- Bio pricefeed: 0x8647dEFdEAAdF5448d021B364B2F17815aba4360
<https://goerli.etherscan.io/address/0x8647defdeaadf5448d021b364b2f17815aba4360#code>
Expand Down
25 changes: 20 additions & 5 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,25 @@ src = 'src'
out = 'out'
libs = ['lib']
test = 'test'
cache_path = 'cache_forge'
cache_path = 'cache_forge'
solc_version = "0.8.18"
gas_reports = ["IPNFT", "IPNFTV2", "SchmackoSwap", "Tokenizer", "IPToken", "CrowdSale", "LockingCrowdSale", "StakedLockingCrowdSale", "TimelockedToken", "TermsAcceptedPermissioner", "SignedMintAuthorizer"]
fs_permissions = [{ access = "read-write", path = "./SALEID.txt"}]
gas_reports = [
"IPNFT",
"IPNFTV2",
"SchmackoSwap",
"Tokenizer",
"IPToken",
"CrowdSale",
"LockingCrowdSale",
"StakedLockingCrowdSale",
"TimelockedToken",
"TermsAcceptedPermissioner",
"SignedMintAuthorizer",
]
fs_permissions = [
{ access = "read-write", path = "./SALEID.txt" },
{ access = "read", path = "./out" },
]

[fmt]
bracket_spacing = true
Expand All @@ -18,5 +33,5 @@ override_spacing = false

[rpc_endpoints]
optimism = "https://optimism-goerli.infura.io/v3/${INFURA_KEY}"
goerli = "https://goerli.infura.io/v3/${INFURA_KEY}"
mainnet = "https://mainnet.infura.io/v3/${INFURA_KEY}"
goerli = "https://goerli.infura.io/v3/${INFURA_KEY}"
mainnet = "https://mainnet.infura.io/v3/${INFURA_KEY}"
2 changes: 1 addition & 1 deletion lib/forge-std
Submodule forge-std updated 56 files
+1 −0 .gitattributes
+134 −0 .github/workflows/ci.yml
+29 −0 .github/workflows/sync.yml
+0 −27 .github/workflows/tests.yml
+1 −1 .gitignore
+1 −1 LICENSE-APACHE
+1 −1 LICENSE-MIT
+8 −4 README.md
+19 −0 foundry.toml
+1 −1 lib/ds-test
+4 −4 package.json
+666 −0 scripts/vm.py
+35 −0 src/Base.sol
+24 −41 src/Script.sol
+376 −0 src/StdAssertions.sol
+244 −0 src/StdChains.sol
+817 −0 src/StdCheats.sol
+15 −0 src/StdError.sol
+107 −0 src/StdInvariant.sol
+126 −61 src/StdJson.sol
+43 −0 src/StdMath.sol
+378 −0 src/StdStorage.sol
+333 −0 src/StdStyle.sol
+226 −0 src/StdUtils.sol
+29 −1,134 src/Test.sol
+1,071 −244 src/Vm.sol
+406 −386 src/console2.sol
+105 −0 src/interfaces/IERC1155.sol
+12 −0 src/interfaces/IERC165.sol
+43 −0 src/interfaces/IERC20.sol
+190 −0 src/interfaces/IERC4626.sol
+164 −0 src/interfaces/IERC721.sol
+73 −0 src/interfaces/IMulticall3.sol
+216 −0 src/mocks/MockERC20.sol
+221 −0 src/mocks/MockERC721.sol
+13,248 −0 src/safeconsole.sol
+0 −20 src/test/Script.t.sol
+0 −602 src/test/StdAssertions.t.sol
+0 −282 src/test/StdCheats.t.sol
+0 −200 src/test/StdMath.t.sol
+1,015 −0 test/StdAssertions.t.sol
+216 −0 test/StdChains.t.sol
+610 −0 test/StdCheats.t.sol
+14 −18 test/StdError.t.sol
+212 −0 test/StdMath.t.sol
+120 −126 test/StdStorage.t.sol
+110 −0 test/StdStyle.t.sol
+342 −0 test/StdUtils.t.sol
+15 −0 test/Vm.t.sol
+10 −0 test/compilation/CompilationScript.sol
+10 −0 test/compilation/CompilationScriptBase.sol
+10 −0 test/compilation/CompilationTest.sol
+10 −0 test/compilation/CompilationTestBase.sol
+0 −0 test/fixtures/broadcast.log.json
+441 −0 test/mocks/MockERC20.t.sol
+721 −0 test/mocks/MockERC721.t.sol
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"license": "MIT",
"scripts": {},
"scripts": {
"test": "hardhat test --network hardhat"
},
"devDependencies": {
"@nomicfoundation/hardhat-foundry": "^1.0.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.6",
Expand Down
16 changes: 16 additions & 0 deletions script/IPToken.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.18;

import "forge-std/Script.sol";
import "../src/IPToken.sol";

contract DeployIPTokenImpl is Script {
function run() external {
vm.startBroadcast();
IPToken iptoken = new IPToken();
vm.stopBroadcast();

console.log("new ip token implementation at %s", address(iptoken));
}
}
24 changes: 24 additions & 0 deletions script/prod/RolloutTokenizerV12.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "forge-std/Script.sol";
import { Tokenizer } from "../../src/Tokenizer.sol";
import { IPToken } from "../../src/IPToken.sol";
import { console } from "forge-std/console.sol";

contract RolloutTokenizerV12 is Script {
function run() public {
vm.startBroadcast();

IPToken newIpTokenImplementation = new IPToken();
Tokenizer newTokenizerImplementation = new Tokenizer();

bytes memory upgradeCallData = abi.encodeWithSelector(Tokenizer.setIPTokenImplementation.selector, address(newIpTokenImplementation));

console.log("NEWTOKENIMPLEMENTATION=%s", address(newIpTokenImplementation));
console.log("NEWTOKENIZER=%s", address(newTokenizerImplementation));
console.logBytes(upgradeCallData);

vm.stopBroadcast();
}
}
4 changes: 4 additions & 0 deletions src/IPToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ contract IPToken is ERC20BurnableUpgradeable, OwnableUpgradeable {
_metadata = metadata_;
}

constructor() {
_disableInitializers();
}

modifier onlyIssuerOrOwner() {
if (_msgSender() != _metadata.originalOwner && _msgSender() != owner()) {
revert OnlyIssuerOrOwner();
Expand Down
39 changes: 32 additions & 7 deletions src/Tokenizer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import { IPNFT } from "./IPNFT.sol";

error MustOwnIpnft();
error AlreadyTokenized();
error ZeroAddress();

/// @title Tokenizer 1.1
/// @title Tokenizer 1.2
/// @author molecule.to
/// @notice tokenizes an IPNFT to an ERC20 token (called IPT) and controls its supply.
/// @notice tokenizes an IPNFT to an ERC20 token (called IPToken or IPT) and controls its supply.
contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
event TokensCreated(
uint256 indexed moleculesId,
Expand All @@ -27,15 +28,23 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
string symbol
);

event IPTokenImplementationUpdated(IPToken indexed old, IPToken indexed _new);
event PermissionerUpdated(IPermissioner indexed old, IPermissioner indexed _new);

IPNFT internal ipnft;

//this is the old term to keep the storage layout intact
/// @dev a map of all IPTs. We're staying with the the initial term "synthesized" to keep the storage layout intact
mapping(uint256 => IPToken) public synthesized;

/// @dev not used, needed to ensure that storage slots are still in order after 1.1 -> 1.2, use ipTokenImplementation
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address immutable tokenImplementation;

/// @dev the permissioner checks if senders have agreed to legal requirements
IPermissioner permissioner;
IPermissioner public permissioner;

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

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

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
tokenImplementation = address(new IPToken());
tokenImplementation = address(0);
_disableInitializers();
}

/**
* @dev called after an upgrade to reinitialize a new permissioner impl. This is 4 for görli compatibility
* @notice sets the new implementation address of the IPToken
* @param _ipTokenImplementation address pointing to the new implementation
*/
function setIPTokenImplementation(IPToken _ipTokenImplementation) external onlyOwner {
/*
could call some functions on old contract to make sure its tokenizer not another contract behind a proxy for safety
*/
if (address(_ipTokenImplementation) == address(0)) {
revert ZeroAddress();
}

emit IPTokenImplementationUpdated(ipTokenImplementation, _ipTokenImplementation);
ipTokenImplementation = _ipTokenImplementation;
}

/**
* @dev called after an upgrade to reinitialize a new permissioner impl.
* @param _permissioner the new TermsPermissioner
*/
function reinit(IPermissioner _permissioner) public onlyOwner reinitializer(4) {
Expand Down Expand Up @@ -84,7 +109,7 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
}

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

Expand Down
113 changes: 113 additions & 0 deletions src/helpers/test-upgrades/Tokenizer11.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { IPToken, Metadata as TokenMetadata } from "../../IPToken.sol";
import { IPermissioner } from "../../Permissioner.sol";
import { IPNFT } from "../../IPNFT.sol";

error MustOwnIpnft();
error AlreadyTokenized();

/// @title Tokenizer 1.1
/// @author molecule.to
/// @notice tokenizes an IPNFT to an ERC20 token (called IPT) and controls its supply.
contract Tokenizer11 is UUPSUpgradeable, OwnableUpgradeable {
event TokensCreated(
uint256 indexed moleculesId,
uint256 indexed ipnftId,
address indexed tokenContract,
address emitter,
uint256 amount,
string agreementCid,
string name,
string symbol
);

IPNFT internal ipnft;

//this is the old term to keep the storage layout intact
mapping(uint256 => IPToken) public synthesized;
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address immutable tokenImplementation;

/// @dev the permissioner checks if senders have agreed to legal requirements
IPermissioner permissioner;

/**
* @param _ipnft the IPNFT contract
* @param _permissioner a permissioning contract that checks if callers have agreed to the tokenized token's legal agreements
*/
function initialize(IPNFT _ipnft, IPermissioner _permissioner) external initializer {
__UUPSUpgradeable_init();
__Ownable_init();
ipnft = _ipnft;
permissioner = _permissioner;
}

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
tokenImplementation = address(new IPToken());
_disableInitializers();
}

/**
* @dev called after an upgrade to reinitialize a new permissioner impl. This is 4 for görli compatibility
* @param _permissioner the new TermsPermissioner
*/
function reinit(IPermissioner _permissioner) public onlyOwner reinitializer(4) {
permissioner = _permissioner;
}

/**
* @notice initializes synthesis on ipnft#id for the current asset holder.
* IPTokens are identified by the original token holder and the token id
* @param ipnftId the token id on the underlying nft collection
* @param tokenAmount the initially issued supply of IP tokens
* @param tokenSymbol the ip token's ticker symbol
* @param agreementCid a content hash that contains legal terms for IP token owners
* @param signedAgreement the sender's signature over the signed agreemeent text (must be created on the client)
* @return token a new created ERC20 token contract that represents the tokenized ipnft
*/
function tokenizeIpnft(
uint256 ipnftId,
uint256 tokenAmount,
string memory tokenSymbol,
string memory agreementCid,
bytes calldata signedAgreement
) external returns (IPToken token) {
if (ipnft.ownerOf(ipnftId) != _msgSender()) {
revert MustOwnIpnft();
}

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

uint256 tokenHash = token.hash();
// ensure we can only call this once per sales cycle
if (address(synthesized[tokenHash]) != address(0)) {
revert AlreadyTokenized();
}

synthesized[tokenHash] = token;

//this has been called MoleculesCreated before
emit TokensCreated(tokenHash, ipnftId, address(token), _msgSender(), tokenAmount, agreementCid, name, tokenSymbol);
permissioner.accept(token, _msgSender(), signedAgreement);
token.issue(_msgSender(), tokenAmount);
}

/// @notice upgrade authorization logic
function _authorizeUpgrade(address /*newImplementation*/ )
internal
override
onlyOwner // solhint-disable--line no-empty-blocks
{
//empty block
}
}
Loading

0 comments on commit 9362140

Please sign in to comment.