From 02e6f81333cb9f9060fee2373560deb8903ad0c0 Mon Sep 17 00:00:00 2001 From: Stefan Adolf Date: Wed, 12 Jul 2023 23:49:20 +0200 Subject: [PATCH 1/6] basic renaming, prepared old code to be tested Signed-off-by: Stefan Adolf renames molecules -> iptoken updates legacy dependencies Signed-off-by: Stefan Adolf dedicated upgrade test Signed-off-by: Stefan Adolf --- script/DeployShareDistributor.s.sol | 2 +- ...ynthesizer.s.sol => DeployTokenizer.s.sol} | 18 +-- script/dev/CrowdSale.s.sol | 10 +- script/dev/SignTermsMessage.s.sol | 2 +- .../{Synthesizer.s.sol => Tokenizer.s.sol} | 28 ++-- script/prod/RolloutV23.sol | 4 +- script/prod/RolloutV23Sale.sol | 1 - script/prod/RolloutV24.sol | 1 - src/IPToken.sol | 122 ++++++++++++++++ src/Permissioner.sol | 24 +-- src/SalesShareDistributor.sol | 18 +-- .../SynthesizerNext.sol => Tokenizer.sol} | 85 +++++------ src/crowdsale/CrowdSale.sol | 10 +- src/{ => helpers/test-upgrades}/Molecules.sol | 0 .../test-upgrades/SynthPermissioner.sol | 84 +++++++++++ .../test-upgrades}/Synthesizer.sol | 4 +- test/CrowdSalePermissioned.t.sol | 6 +- test/Permissioner.t.sol | 24 +-- test/SalesShareDistributor.t.sol | 42 +++--- test/SynthesizerUpgrade.t.sol | 137 ++++++++++++++++++ test/{Synthesizer.t.sol => Tokenizer.t.sol} | 102 ++++++------- 21 files changed, 530 insertions(+), 194 deletions(-) rename script/{DeploySynthesizer.s.sol => DeployTokenizer.s.sol} (71%) rename script/dev/{Synthesizer.s.sol => Tokenizer.s.sol} (67%) create mode 100644 src/IPToken.sol rename src/{helpers/test-upgrades/SynthesizerNext.sol => Tokenizer.sol} (51%) rename src/{ => helpers/test-upgrades}/Molecules.sol (100%) create mode 100644 src/helpers/test-upgrades/SynthPermissioner.sol rename src/{ => helpers/test-upgrades}/Synthesizer.sol (97%) create mode 100644 test/SynthesizerUpgrade.t.sol rename test/{Synthesizer.t.sol => Tokenizer.t.sol} (66%) diff --git a/script/DeployShareDistributor.s.sol b/script/DeployShareDistributor.s.sol index 7d6fcc4a..1672ad2c 100644 --- a/script/DeployShareDistributor.s.sol +++ b/script/DeployShareDistributor.s.sol @@ -6,7 +6,7 @@ import "forge-std/console.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { IPNFT } from "../src/IPNFT.sol"; import { SchmackoSwap } from "../src/SchmackoSwap.sol"; -import { Synthesizer } from "../src/Synthesizer.sol"; +import { Tokenizer } from "../src/Tokenizer.sol"; import { SalesShareDistributor } from "../src/SalesShareDistributor.sol"; contract DeployShareDistributor is Script { diff --git a/script/DeploySynthesizer.s.sol b/script/DeployTokenizer.s.sol similarity index 71% rename from script/DeploySynthesizer.s.sol rename to script/DeployTokenizer.s.sol index 2f5b6e8d..d2f12104 100644 --- a/script/DeploySynthesizer.s.sol +++ b/script/DeployTokenizer.s.sol @@ -4,27 +4,27 @@ pragma solidity ^0.8.18; import "forge-std/Script.sol"; import "forge-std/console.sol"; import { IPNFT } from "../src/IPNFT.sol"; -import { Synthesizer } from "../src/Synthesizer.sol"; +import { Tokenizer } from "../src/Tokenizer.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { BioPriceFeed } from "../src/BioPriceFeed.sol"; import { IPermissioner, TermsAcceptedPermissioner } from "../src/Permissioner.sol"; import { StakedLockingCrowdSale } from "../src/crowdsale/StakedLockingCrowdSale.sol"; -contract DeploySynthesizerInfrastructure is Script { +contract DeployTokenizerInfrastructure is Script { function run() public { vm.startBroadcast(); address ipnftAddress = vm.envAddress("IPNFT_ADDRESS"); BioPriceFeed feed = new BioPriceFeed(); IPermissioner p = new TermsAcceptedPermissioner(); - Synthesizer synthesizer = Synthesizer( + Tokenizer tokenizer = Tokenizer( address( new ERC1967Proxy( - address(new Synthesizer()), "" + address(new Tokenizer()), "" ) ) ); - synthesizer.initialize(IPNFT(ipnftAddress), p); + tokenizer.initialize(IPNFT(ipnftAddress), p); StakedLockingCrowdSale stakedLockingCrowdSale = new StakedLockingCrowdSale(); @@ -32,17 +32,17 @@ contract DeploySynthesizerInfrastructure is Script { console.log("PRICEFEED_ADDRESS=%s", address(feed)); console.log("TERMS_ACCEPTED_PERMISSIONER_ADDRESS=%s", address(p)); - console.log("SYNTHESIZER_ADDRESS=%s", address(synthesizer)); + console.log("TOKENIZER_ADDRESS=%s", address(tokenizer)); console.log("STAKED_LOCKING_CROWDSALE_ADDRESS=%s", address(stakedLockingCrowdSale)); } } -contract DeploySynthesizerImplementation is Script { +contract DeploytokenizerImplementation is Script { function run() public { vm.startBroadcast(); - Synthesizer impl = new Synthesizer(); + Tokenizer impl = new Tokenizer(); vm.stopBroadcast(); - console.log("synthesizer impl %s", address(impl)); + console.log("tokenizer impl %s", address(impl)); } } diff --git a/script/dev/CrowdSale.s.sol b/script/dev/CrowdSale.s.sol index c13d3b69..92dba281 100644 --- a/script/dev/CrowdSale.s.sol +++ b/script/dev/CrowdSale.s.sol @@ -16,7 +16,7 @@ import { IPermissioner, TermsAcceptedPermissioner } from "../../src/Permissioner import { CrowdSale, Sale, SaleInfo } from "../../src/crowdsale/CrowdSale.sol"; import { StakedLockingCrowdSale } from "../../src/crowdsale/StakedLockingCrowdSale.sol"; import { FakeERC20 } from "../../src/helpers/FakeERC20.sol"; -import { Molecules } from "../../src/Molecules.sol"; +import { IPToken } from "../../src/IPToken.sol"; import { CommonScript } from "./Common.sol"; @@ -49,7 +49,7 @@ contract FixtureCrowdSale is CommonScript { FakeERC20 daoToken; TokenVesting vestedDaoToken; - Molecules internal auctionToken; + IPToken internal auctionToken; StakedLockingCrowdSale stakedLockingCrowdSale; TermsAcceptedPermissioner permissioner; @@ -61,7 +61,7 @@ contract FixtureCrowdSale is CommonScript { daoToken = FakeERC20(vm.envAddress("DAO_TOKEN_ADDRESS")); vestedDaoToken = TokenVesting(vm.envAddress("VDAO_TOKEN_ADDRESS")); - auctionToken = Molecules(vm.envAddress("MOLECULES_ADDRESS")); + auctionToken = IPToken(vm.envAddress("MOLECULES_ADDRESS")); stakedLockingCrowdSale = StakedLockingCrowdSale(vm.envAddress("STAKED_LOCKING_CROWDSALE_ADDRESS")); permissioner = TermsAcceptedPermissioner(vm.envAddress("TERMS_ACCEPTED_PERMISSIONER_ADDRESS")); @@ -69,7 +69,7 @@ contract FixtureCrowdSale is CommonScript { function setupVestedMolToken() internal { vm.startBroadcast(deployer); - auctionToken = Molecules(vm.envAddress("MOLECULES_ADDRESS")); + auctionToken = IPToken(vm.envAddress("MOLECULES_ADDRESS")); vestedDaoToken.grantRole(vestedDaoToken.ROLE_CREATE_SCHEDULE(), address(stakedLockingCrowdSale)); vm.stopBroadcast(); @@ -128,7 +128,7 @@ contract ClaimSale is CommonScript { prepareAddresses(); TermsAcceptedPermissioner permissioner = TermsAcceptedPermissioner(vm.envAddress("TERMS_ACCEPTED_PERMISSIONER_ADDRESS")); StakedLockingCrowdSale stakedLockingCrowdSale = StakedLockingCrowdSale(vm.envAddress("STAKED_LOCKING_CROWDSALE_ADDRESS")); - Molecules auctionToken = Molecules(vm.envAddress("MOLECULES_ADDRESS")); + IPToken auctionToken = IPToken(vm.envAddress("MOLECULES_ADDRESS")); uint256 saleId = vm.envUint("SALE_ID"); diff --git a/script/dev/SignTermsMessage.s.sol b/script/dev/SignTermsMessage.s.sol index 90df523e..a8a78999 100644 --- a/script/dev/SignTermsMessage.s.sol +++ b/script/dev/SignTermsMessage.s.sol @@ -9,7 +9,7 @@ contract SignTermsMessage is Script { function run() public { uint256 pk = vm.envUint("PRIVATE_KEY"); string memory terms = - "As a molecule holder of IPNFT #10, I accept all terms that I've read here: ipfs://bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq\n\nChain Id: 31337\nVersion: 1"; + "As an IP token holder of IPNFT #10, I accept all terms that I've read here: ipfs://bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq\n\nChain Id: 31337\nVersion: 1"; bytes32 termsHash = ECDSA.toEthSignedMessageHash(abi.encodePacked(terms)); diff --git a/script/dev/Synthesizer.s.sol b/script/dev/Tokenizer.s.sol similarity index 67% rename from script/dev/Synthesizer.s.sol rename to script/dev/Tokenizer.s.sol index 3647dd1a..d14a2c4b 100644 --- a/script/dev/Synthesizer.s.sol +++ b/script/dev/Tokenizer.s.sol @@ -4,45 +4,45 @@ pragma solidity ^0.8.13; import "forge-std/Script.sol"; import "forge-std/console.sol"; import { IPNFT } from "../../src/IPNFT.sol"; -import { Synthesizer } from "../../src/Synthesizer.sol"; -import { Metadata, Molecules } from "../../src/Molecules.sol"; +import { Tokenizer } from "../../src/Tokenizer.sol"; +import { Metadata, IPToken } from "../../src/IPToken.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { IPermissioner, TermsAcceptedPermissioner } from "../../src/Permissioner.sol"; import { CommonScript } from "./Common.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -contract DeploySynthesizer is CommonScript { +contract DeployTokenizer is CommonScript { function run() public { prepareAddresses(); vm.startBroadcast(deployer); - Synthesizer synthesizer = Synthesizer( + Tokenizer tokenizer = Tokenizer( address( new ERC1967Proxy( - address(new Synthesizer()), "" + address(new Tokenizer()), "" ) ) ); IPermissioner permissioner = IPermissioner(vm.envAddress("TERMS_ACCEPTED_PERMISSIONER_ADDRESS")); - synthesizer.initialize(IPNFT(vm.envAddress("IPNFT_ADDRESS")), permissioner); + tokenizer.initialize(IPNFT(vm.envAddress("IPNFT_ADDRESS")), permissioner); vm.stopBroadcast(); - console.log("SYNTHESIZER_ADDRESS=%s", address(synthesizer)); + console.log("SYNTHESIZER_ADDRESS=%s", address(tokenizer)); } } /** - * @title FixtureSynthesizer + * @title FixtureTokenizer * @author - * @notice execute Ipnft.s.sol && DeploySynthesizer first + * @notice execute Ipnft.s.sol && DeployTokenizer first * @notice assumes that bob (hh1) owns IPNFT#1 */ -contract FixtureSynthesizer is CommonScript { - Synthesizer synthesizer; +contract FixtureTokenizer is CommonScript { + Tokenizer tokenizer; TermsAcceptedPermissioner permissioner; function prepareAddresses() internal override { super.prepareAddresses(); - synthesizer = Synthesizer(vm.envAddress("SYNTHESIZER_ADDRESS")); + tokenizer = Tokenizer(vm.envAddress("SYNTHESIZER_ADDRESS")); permissioner = TermsAcceptedPermissioner(vm.envAddress("TERMS_ACCEPTED_PERMISSIONER_ADDRESS")); } @@ -54,8 +54,8 @@ contract FixtureSynthesizer is CommonScript { vm.startBroadcast(bob); (uint8 v, bytes32 r, bytes32 s) = vm.sign(bobPk, ECDSA.toEthSignedMessageHash(abi.encodePacked(terms))); bytes memory signedTerms = abi.encodePacked(r, s, v); - Molecules tokenContract = - synthesizer.synthesizeIpnft(1, 1_000_000 ether, "MOLE", "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq", signedTerms); + IPToken tokenContract = + tokenizer.tokenizeIpnft(1, 1_000_000 ether, "MOLE", "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq", signedTerms); vm.stopBroadcast(); console.log("MOLECULES_ADDRESS=%s", address(tokenContract)); diff --git a/script/prod/RolloutV23.sol b/script/prod/RolloutV23.sol index 3fdb9738..fe8cbfaa 100644 --- a/script/prod/RolloutV23.sol +++ b/script/prod/RolloutV23.sol @@ -5,8 +5,8 @@ import "forge-std/Script.sol"; import "forge-std/console.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { IPNFT } from "../../src/IPNFT.sol"; -import { IPermissioner, TermsAcceptedPermissioner } from "../../src/Permissioner.sol"; -import { Synthesizer } from "../../src/Synthesizer.sol"; +import { IPermissioner, TermsAcceptedPermissioner } from "../../src/helpers/test-upgrades/SynthPermissioner.sol"; +import { Synthesizer } from "../../src/helpers/test-upgrades/Synthesizer.sol"; import { StakedLockingCrowdSale } from "../../src/crowdsale/StakedLockingCrowdSale.sol"; import { Mintpass } from "../../src/Mintpass.sol"; diff --git a/script/prod/RolloutV23Sale.sol b/script/prod/RolloutV23Sale.sol index fe608114..8e4b4403 100644 --- a/script/prod/RolloutV23Sale.sol +++ b/script/prod/RolloutV23Sale.sol @@ -6,7 +6,6 @@ import "forge-std/console.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { IPNFT } from "../../src/IPNFT.sol"; import { IPermissioner, TermsAcceptedPermissioner } from "../../src/Permissioner.sol"; -import { Synthesizer } from "../../src/Synthesizer.sol"; import { StakedLockingCrowdSale } from "../../src/crowdsale/StakedLockingCrowdSale.sol"; contract RolloutV23Sale is Script { diff --git a/script/prod/RolloutV24.sol b/script/prod/RolloutV24.sol index da454233..2ee55e24 100644 --- a/script/prod/RolloutV24.sol +++ b/script/prod/RolloutV24.sol @@ -6,7 +6,6 @@ import "forge-std/console.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { IPNFT } from "../../src/IPNFT.sol"; import { IPermissioner, TermsAcceptedPermissioner } from "../../src/Permissioner.sol"; -import { Synthesizer } from "../../src/Synthesizer.sol"; import { StakedLockingCrowdSale } from "../../src/crowdsale/StakedLockingCrowdSale.sol"; import { SignedMintAuthorizer } from "../../src/SignedMintAuthorizer.sol"; diff --git a/src/IPToken.sol b/src/IPToken.sol new file mode 100644 index 00000000..7a044b37 --- /dev/null +++ b/src/IPToken.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import { ERC20BurnableUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { Base64 } from "@openzeppelin/contracts/utils/Base64.sol"; + +struct Metadata { + uint256 ipnftId; + address originalOwner; + string agreementCid; +} + +error TokenCapped(); +error OnlyIssuerOrOwner(); + +/** + * @title IPToken + * @author molecule.to + * @notice this is a template contract that's spawned by the Tokenizer + * @notice the owner of this contract is always the Tokenizer contract. + * the issuer of a token bears the right to increase the supply as long as the token is not capped. + * @dev formerly known as "molecules" + */ +contract IPToken is ERC20BurnableUpgradeable, OwnableUpgradeable { + event Capped(uint256 atSupply); + + //this will only go up. + uint256 public totalIssued; + /** + * @notice when true, no one can ever mint tokens again. + */ + bool public capped; + Metadata internal _metadata; + + function initialize(string calldata name, string calldata symbol, Metadata calldata metadata_) external initializer { + __Ownable_init(); + __ERC20_init(name, symbol); + _metadata = metadata_; + } + + modifier onlyIssuerOrOwner() { + if (_msgSender() != _metadata.originalOwner && _msgSender() != owner()) { + revert OnlyIssuerOrOwner(); + } + _; + } + + function issuer() external view returns (address) { + return _metadata.originalOwner; + } + + function metadata() external view returns (Metadata memory) { + return _metadata; + } + /** + * @notice ip tokens are identified by the original ipnft token holder and the underlying ip token id + * @return uint256 a token hash that's unique for [`originaOwner`,`ipnftid`] + */ + + function hash() external view returns (uint256) { + return uint256(keccak256(abi.encodePacked(_metadata.originalOwner, _metadata.ipnftId))); + } + + /** + * @notice we deliberately allow the synthesis initializer to increase the supply of IP Tokens at will as long as the underlying asset has not been sold yet + * @param receiver address + * @param amount uint256 + */ + function issue(address receiver, uint256 amount) external onlyIssuerOrOwner { + if (capped) revert TokenCapped(); + totalIssued += amount; + _mint(receiver, amount); + } + + /** + * @notice mark this token as capped. After calling this, no new tokens can be `issue`d + */ + function cap() external onlyIssuerOrOwner { + capped = true; + emit Capped(totalIssued); + } + + /** + * @notice contract metadata, compatible to ERC1155 + * @return string base64 encoded data url + */ + function uri() external view returns (string memory) { + string memory tokenId = Strings.toString(_metadata.ipnftId); + + string memory props = string.concat( + '"properties": {', + '"ipnft_id": ', + tokenId, + ',"agreement_content": "ipfs://', + _metadata.agreementCid, + '","original_owner": "', + Strings.toHexString(_metadata.originalOwner), + '","erc20_contract": "', + Strings.toHexString(address(this)), + '","supply": "', + Strings.toString(totalIssued), + '"}' + ); + + return string.concat( + "data:application/json;base64,", + Base64.encode( + bytes( + string.concat( + '{"name": "IP Tokens of IPNFT #', + tokenId, + '","description": "IP Tokens, derived from IP-NFTs, are ERC-20 tokens governing IP pools.","decimals": 18,"external_url": "https://molecule.to","image": "",', + props, + "}" + ) + ) + ) + ); + } +} diff --git a/src/Permissioner.sol b/src/Permissioner.sol index 4157cb55..b6ffa250 100644 --- a/src/Permissioner.sol +++ b/src/Permissioner.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.18; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import { Molecules, Metadata } from "./Molecules.sol"; +import { IPToken, Metadata } from "./IPToken.sol"; error InvalidSignature(); error Denied(); @@ -12,21 +12,21 @@ error Denied(); interface IPermissioner { /** * @notice reverts when `_for` may not interact with `tokenContract` - * @param tokenContract IMolecules + * @param tokenContract IPToken * @param _for address * @param data bytes */ - function accept(Molecules tokenContract, address _for, bytes calldata data) external; + function accept(IPToken tokenContract, address _for, bytes calldata data) external; } contract BlindPermissioner is IPermissioner { - function accept(Molecules tokenContract, address _for, bytes calldata data) external { + function accept(IPToken tokenContract, address _for, bytes calldata data) external { //empty } } contract ForbidAllPermissioner is IPermissioner { - function accept(Molecules, address, bytes calldata) external pure { + function accept(IPToken, address, bytes calldata) external pure { revert Denied(); } } @@ -35,16 +35,16 @@ contract TermsAcceptedPermissioner is IPermissioner { event TermsAccepted(address indexed tokenContract, address indexed signer, bytes signature); /** - * @notice checks validity signer`'s `signature` of `specificTermsV1` on `moleculesId` and emits an event + * @notice checks validity signer`'s `signature` of `specificTermsV1` on `tokenId` and emits an event * reverts when `signature` can't be verified * @dev the signature itself or whether it has already been presented is not stored on chain * uses OZ:`SignatureChecker` under the hood and also supports EIP1271 signatures * - * @param tokenContract Molecules + * @param tokenContract IPToken * @param _for address the account that has created `signature` * @param signature bytes encoded signature, for eip155: `abi.encodePacked(r, s, v)` */ - function accept(Molecules tokenContract, address _for, bytes calldata signature) external { + function accept(IPToken tokenContract, address _for, bytes calldata signature) external { if (!isValidSignature(tokenContract, _for, signature)) { revert InvalidSignature(); } @@ -52,17 +52,17 @@ contract TermsAcceptedPermissioner is IPermissioner { } /** - * @notice checks whether `signer`'s `signature` of `specificTermsV1` on `moleculeId` is valid + * @notice checks whether `signer`'s `signature` of `specificTermsV1` on `tokenContract.metadata.ipnftId` is valid * @param tokenContract Molecules */ - function isValidSignature(Molecules tokenContract, address signer, bytes calldata signature) public view returns (bool) { + function isValidSignature(IPToken tokenContract, address signer, bytes calldata signature) public view returns (bool) { bytes32 termsHash = ECDSA.toEthSignedMessageHash(bytes(specificTermsV1(tokenContract))); return SignatureChecker.isValidSignatureNow(signer, termsHash, signature); } function specificTermsV1(Metadata memory metadata) public view returns (string memory) { return string.concat( - "As a molecule holder of IPNFT #", + "As an IP token holder of IPNFT #", Strings.toString(metadata.ipnftId), ", I accept all terms that I've read here: ipfs://", metadata.agreementCid, @@ -78,7 +78,7 @@ contract TermsAcceptedPermissioner is IPermissioner { * @notice this yields the message text that claimers must present as signed message to burn their molecules and claim shares * @param tokenContract ISynthesizedToken */ - function specificTermsV1(Molecules tokenContract) public view returns (string memory) { + function specificTermsV1(IPToken tokenContract) public view returns (string memory) { return (specificTermsV1(tokenContract.metadata())); } } diff --git a/src/SalesShareDistributor.sol b/src/SalesShareDistributor.sol index 09cf9a09..1cd3c1df 100644 --- a/src/SalesShareDistributor.sol +++ b/src/SalesShareDistributor.sol @@ -7,7 +7,7 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { Molecules, Metadata } from "./Molecules.sol"; +import { IPToken, Metadata } from "./IPToken.sol"; import { SchmackoSwap, ListingState } from "./SchmackoSwap.sol"; import { IPermissioner, TermsAcceptedPermissioner } from "./Permissioner.sol"; @@ -46,7 +46,7 @@ contract SalesShareDistributor is UUPSUpgradeable, OwnableUpgradeable, Reentranc * @param tokenContract address * @param holder address */ - function claimableTokens(Molecules tokenContract, address holder) public view returns (IERC20 paymentToken, uint256 amount) { + function claimableTokens(IPToken tokenContract, address holder) public view returns (IERC20 paymentToken, uint256 amount) { Sales storage _sales = sales[address(tokenContract)]; if (address(_sales.paymentToken) == address(0)) { @@ -63,7 +63,7 @@ contract SalesShareDistributor is UUPSUpgradeable, OwnableUpgradeable, Reentranc * @param permissions bytes data that can be read and verified by the configured permissioner * at the moment this simply is a valid signature over a `specificTermsV1` message */ - function claim(Molecules tokenContract, bytes memory permissions) public nonReentrant { + function claim(IPToken tokenContract, bytes memory permissions) public nonReentrant { uint256 balance = tokenContract.balanceOf(_msgSender()); if (balance < 1000) { revert InsufficientBalance(); @@ -88,11 +88,11 @@ contract SalesShareDistributor is UUPSUpgradeable, OwnableUpgradeable, Reentranc * rn we restrict it to the token issuer since they must provide a permissioner that controls the claiming rules * this is a deep dependency on our own sales contract * - * @param tokenContract Molecules the tokenContract of the Molecules + * @param tokenContract IPToken the tokenContract of the IPToken * @param listingId uint256 the listing id on Schmackoswap * @param permissioner IPermissioner the permissioner that permits claims */ - function afterSale(Molecules tokenContract, uint256 listingId, IPermissioner permissioner) external { + function afterSale(IPToken tokenContract, uint256 listingId, IPermissioner permissioner) external { if (_msgSender() != tokenContract.issuer()) { revert OnlyIssuer(); } @@ -115,7 +115,7 @@ contract SalesShareDistributor is UUPSUpgradeable, OwnableUpgradeable, Reentranc } //audit: ensure that no one can withdraw arbitrary amounts here - //by simply creating a new Molecules instance and claim an arbitrary value + //by simply creating a new IPToken instance and claim an arbitrary value /** * @notice When the sales beneficiary has not been set to the underlying erc20 token address but to the original owner's wallet instead, @@ -124,12 +124,12 @@ contract SalesShareDistributor is UUPSUpgradeable, OwnableUpgradeable, Reentranc * Requires the originalOwner to behave honestly / in favor of the molecules holders * Requires the caller to have approved `price` of `paymentToken` to this contract * - * @param tokenContract Molecules the Molecules token contract + * @param tokenContract IPToken the IPToken token contract * @param paymentToken IERC20 the payment token contract address * @param paidPrice uint256 the price the NFT has been sold for * @param permissioner IPermissioner the permissioner that permits claims */ - function afterSale(Molecules tokenContract, IERC20 paymentToken, uint256 paidPrice, IPermissioner permissioner) external nonReentrant { + function afterSale(IPToken tokenContract, IERC20 paymentToken, uint256 paidPrice, IPermissioner permissioner) external nonReentrant { if (_msgSender() != tokenContract.issuer()) { revert OnlyIssuer(); } @@ -157,7 +157,7 @@ contract SalesShareDistributor is UUPSUpgradeable, OwnableUpgradeable, Reentranc paymentToken.safeTransferFrom(_msgSender(), address(this), paidPrice); } - function _startClaimingPhase(Molecules tokenContract, uint256 fulfilledListingId, IERC20 _paymentToken, uint256 price, IPermissioner permissioner) + function _startClaimingPhase(IPToken tokenContract, uint256 fulfilledListingId, IERC20 _paymentToken, uint256 price, IPermissioner permissioner) internal { if (!tokenContract.capped()) { diff --git a/src/helpers/test-upgrades/SynthesizerNext.sol b/src/Tokenizer.sol similarity index 51% rename from src/helpers/test-upgrades/SynthesizerNext.sol rename to src/Tokenizer.sol index 3fcbed09..b1705407 100644 --- a/src/helpers/test-upgrades/SynthesizerNext.sol +++ b/src/Tokenizer.sol @@ -5,27 +5,18 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils 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 { Molecules, Metadata as MoleculesMetadata } from "../../Molecules.sol"; -import { IPermissioner } from "../../Permissioner.sol"; -import { IPNFT } from "../../IPNFT.sol"; +import { IPToken, Metadata as TokenMetadata } from "./IPToken.sol"; +import { IPermissioner } from "./Permissioner.sol"; +import { IPNFT } from "./IPNFT.sol"; error MustOwnIpnft(); -error AlreadySynthesized(); +error AlreadyTokenized(); -contract MoleculesNext is Molecules { - uint256 public aNewStateVar; - - function setAStateVar(uint256 newVal) public { - aNewStateVar = newVal; - } -} - -/// @title Synthesizer +/// @title Tokenizer 1.1 /// @author molecule.to -/// @notice synthesizes an IPNFT to an ERC20 token (called molecules) and controls its supply. -/// Allows molecule holders to withdraw sales shares when the IPNFT is sold -contract SynthesizerNext is UUPSUpgradeable, OwnableUpgradeable { - event MoleculesCreated( +/// @notice tokenizes an IPNFT to an ERC20 token (called IPT) and controls its supply. +contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable { + event TokensCreated( uint256 indexed moleculesId, uint256 indexed ipnftId, address indexed tokenContract, @@ -38,7 +29,7 @@ contract SynthesizerNext is UUPSUpgradeable, OwnableUpgradeable { IPNFT internal ipnft; - mapping(uint256 => MoleculesNext) public synthesized; + mapping(uint256 => IPToken) public tokenized; /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address immutable tokenImplementation; @@ -46,9 +37,8 @@ contract SynthesizerNext is UUPSUpgradeable, OwnableUpgradeable { IPermissioner permissioner; /** - * * @param _ipnft the IPNFT contract - * @param _permissioner a permissioning contract that checks if callers have agreed to the synthesized token's legal agreements + * @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(); @@ -57,56 +47,61 @@ contract SynthesizerNext is UUPSUpgradeable, OwnableUpgradeable { permissioner = _permissioner; } - function reinit(IPermissioner _permissioner) public onlyOwner reinitializer(3) { - permissioner = _permissioner; - } - /// @custom:oz-upgrades-unsafe-allow constructor constructor() { - tokenImplementation = address(new MoleculesNext()); + tokenImplementation = address(new IPToken()); _disableInitializers(); } + function reinit(IPermissioner _permissioner) public onlyOwner reinitializer(2) { + permissioner = _permissioner; + } + /** * @notice initializes synthesis on ipnft#id for the current asset holder. - * molecules are identified by the original token holder and the token id + * IPTokens are identified by the original token holder and the token id * @param ipnftId the token id on the underlying nft collection - * @param moleculesAmount the initially issued supply of Molecules - * @param agreementCid a content hash that contains legal terms for Molecule owners + * @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 molecules a new created ERC20 token contract that represents the molecules + * @return token a new created ERC20 token contract that represents the tokenized ipnft */ - function synthesizeIpnft(uint256 ipnftId, uint256 moleculesAmount, string calldata agreementCid, bytes calldata signedAgreement) - external - returns (MoleculesNext molecules) - { + 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(); } - string memory tokenSymbol = ipnft.symbol(ipnftId); + // https://github.com/OpenZeppelin/workshops/tree/master/02-contracts-clone - molecules = MoleculesNext(Clones.clone(tokenImplementation)); - string memory name = string.concat("Molecules of IPNFT #", Strings.toString(ipnftId)); - molecules.initialize(name, tokenSymbol, MoleculesMetadata(ipnftId, _msgSender(), agreementCid)); + 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 moleculeHash = molecules.hash(); + uint256 tokenHash = token.hash(); // ensure we can only call this once per sales cycle - if (address(synthesized[moleculeHash]) != address(0)) { - revert AlreadySynthesized(); + if (address(tokenized[tokenHash]) != address(0)) { + revert AlreadyTokenized(); } - synthesized[moleculeHash] = molecules; + tokenized[tokenHash] = token; - emit MoleculesCreated(moleculeHash, ipnftId, address(molecules), _msgSender(), moleculesAmount, agreementCid, name, tokenSymbol); - permissioner.accept(molecules, _msgSender(), signedAgreement); - molecules.issue(_msgSender(), moleculesAmount); + //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-next-line no-empty-blocks + onlyOwner // solhint-disable--line no-empty-blocks { //empty block } diff --git a/src/crowdsale/CrowdSale.sol b/src/crowdsale/CrowdSale.sol index 5c683837..53f9c8ec 100644 --- a/src/crowdsale/CrowdSale.sol +++ b/src/crowdsale/CrowdSale.sol @@ -6,7 +6,7 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol"; import { IPermissioner } from "../Permissioner.sol"; -import { Molecules } from "../Molecules.sol"; +import { IPToken } from "../IPToken.sol"; enum SaleState { UNKNOWN, @@ -126,7 +126,7 @@ contract CrowdSale is ReentrancyGuard { } /** - * @dev even though `auctionToken` is casted to `Molecules` this should still work with IPNFT agnostic tokens + * @dev even though `auctionToken` is casted to `IPToken` this should still work with IPNFT agnostic tokens * @param saleId the sale id * @param biddingTokenAmount the amount of bidding tokens * @param permission bytes are handed over to a configured permissioner contract. Set to 0x0 / "" / [] if not needed @@ -148,7 +148,7 @@ contract CrowdSale is ReentrancyGuard { } if (address(sale.permissioner) != address(0)) { - sale.permissioner.accept(Molecules(address(sale.auctionToken)), msg.sender, permission); + sale.permissioner.accept(IPToken(address(sale.auctionToken)), msg.sender, permission); } _bid(saleId, biddingTokenAmount); @@ -231,7 +231,7 @@ contract CrowdSale is ReentrancyGuard { } /** - * @dev even though `auctionToken` is casted to `Molecules` this should still work with IPNFT agnostic tokens + * @dev even though `auctionToken` is casted to `IPToken` this should still work with IPNFT agnostic tokens * @notice public method that refunds and lets user redeem their sales shares * @param saleId the sale id * @param permission. bytes are handed over to a configured permissioner contract @@ -249,7 +249,7 @@ contract CrowdSale is ReentrancyGuard { Sale storage sales = _sales[saleId]; //we're not querying the permissioner if the sale has failed. if (address(sales.permissioner) != address(0)) { - sales.permissioner.accept(Molecules(address(sales.auctionToken)), msg.sender, permission); + sales.permissioner.accept(IPToken(address(sales.auctionToken)), msg.sender, permission); } (auctionTokens, refunds) = getClaimableAmounts(saleId, msg.sender); //a reentrancy won't have any effect after setting this to 0. diff --git a/src/Molecules.sol b/src/helpers/test-upgrades/Molecules.sol similarity index 100% rename from src/Molecules.sol rename to src/helpers/test-upgrades/Molecules.sol diff --git a/src/helpers/test-upgrades/SynthPermissioner.sol b/src/helpers/test-upgrades/SynthPermissioner.sol new file mode 100644 index 00000000..4157cb55 --- /dev/null +++ b/src/helpers/test-upgrades/SynthPermissioner.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import { Molecules, Metadata } from "./Molecules.sol"; + +error InvalidSignature(); +error Denied(); + +interface IPermissioner { + /** + * @notice reverts when `_for` may not interact with `tokenContract` + * @param tokenContract IMolecules + * @param _for address + * @param data bytes + */ + function accept(Molecules tokenContract, address _for, bytes calldata data) external; +} + +contract BlindPermissioner is IPermissioner { + function accept(Molecules tokenContract, address _for, bytes calldata data) external { + //empty + } +} + +contract ForbidAllPermissioner is IPermissioner { + function accept(Molecules, address, bytes calldata) external pure { + revert Denied(); + } +} + +contract TermsAcceptedPermissioner is IPermissioner { + event TermsAccepted(address indexed tokenContract, address indexed signer, bytes signature); + + /** + * @notice checks validity signer`'s `signature` of `specificTermsV1` on `moleculesId` and emits an event + * reverts when `signature` can't be verified + * @dev the signature itself or whether it has already been presented is not stored on chain + * uses OZ:`SignatureChecker` under the hood and also supports EIP1271 signatures + * + * @param tokenContract Molecules + * @param _for address the account that has created `signature` + * @param signature bytes encoded signature, for eip155: `abi.encodePacked(r, s, v)` + */ + function accept(Molecules tokenContract, address _for, bytes calldata signature) external { + if (!isValidSignature(tokenContract, _for, signature)) { + revert InvalidSignature(); + } + emit TermsAccepted(address(tokenContract), _for, signature); + } + + /** + * @notice checks whether `signer`'s `signature` of `specificTermsV1` on `moleculeId` is valid + * @param tokenContract Molecules + */ + function isValidSignature(Molecules tokenContract, address signer, bytes calldata signature) public view returns (bool) { + bytes32 termsHash = ECDSA.toEthSignedMessageHash(bytes(specificTermsV1(tokenContract))); + return SignatureChecker.isValidSignatureNow(signer, termsHash, signature); + } + + function specificTermsV1(Metadata memory metadata) public view returns (string memory) { + return string.concat( + "As a molecule holder of IPNFT #", + Strings.toString(metadata.ipnftId), + ", I accept all terms that I've read here: ipfs://", + metadata.agreementCid, + "\n\n", + "Chain Id: ", + Strings.toString(block.chainid), + "\n", + "Version: 1" + ); + } + + /** + * @notice this yields the message text that claimers must present as signed message to burn their molecules and claim shares + * @param tokenContract ISynthesizedToken + */ + function specificTermsV1(Molecules tokenContract) public view returns (string memory) { + return (specificTermsV1(tokenContract.metadata())); + } +} diff --git a/src/Synthesizer.sol b/src/helpers/test-upgrades/Synthesizer.sol similarity index 97% rename from src/Synthesizer.sol rename to src/helpers/test-upgrades/Synthesizer.sol index 9cb371be..de4af7e7 100644 --- a/src/Synthesizer.sol +++ b/src/helpers/test-upgrades/Synthesizer.sol @@ -6,8 +6,8 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { Molecules, Metadata as MoleculesMetadata } from "./Molecules.sol"; -import { IPermissioner } from "./Permissioner.sol"; -import { IPNFT } from "./IPNFT.sol"; +import { IPermissioner } from "./SynthPermissioner.sol"; +import { IPNFT } from "../../IPNFT.sol"; error MustOwnIpnft(); error AlreadySynthesized(); diff --git a/test/CrowdSalePermissioned.t.sol b/test/CrowdSalePermissioned.t.sol index 4c1c125f..b3914a1b 100644 --- a/test/CrowdSalePermissioned.t.sol +++ b/test/CrowdSalePermissioned.t.sol @@ -8,7 +8,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import { Molecules, Metadata } from "../src/Molecules.sol"; +import { IPToken, Metadata } from "../src/IPToken.sol"; import { CrowdSale, Sale, SaleInfo, SaleState, BadDecimals } from "../src/crowdsale/CrowdSale.sol"; import { StakedLockingCrowdSale, BadPrice } from "../src/crowdsale/StakedLockingCrowdSale.sol"; import { IPermissioner, TermsAcceptedPermissioner, InvalidSignature } from "../src/Permissioner.sol"; @@ -27,7 +27,7 @@ contract CrowdSalePermissionedTest is Test { address anyone = makeAddr("anyone"); - Molecules internal auctionToken; + IPToken internal auctionToken; FakeERC20 internal biddingToken; FakeERC20 internal daoToken; @@ -43,7 +43,7 @@ contract CrowdSalePermissionedTest is Test { (bidder, bidderPk) = makeAddrAndKey("bidder"); vm.startPrank(deployer); - auctionToken = new Molecules(); + auctionToken = new IPToken(); auctionToken.initialize("MOLECULES", "MOL-0001", Metadata(42, msg.sender, "ipfs://abcde")); biddingToken = new FakeERC20("USD token", "USDC"); diff --git a/test/Permissioner.t.sol b/test/Permissioner.t.sol index c93868e4..cd1b9d1c 100644 --- a/test/Permissioner.t.sol +++ b/test/Permissioner.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.18; import "forge-std/Test.sol"; import { InvalidSignature, IPermissioner, TermsAcceptedPermissioner } from "../src/Permissioner.sol"; -import { Molecules, Metadata } from "../src/Molecules.sol"; +import { IPToken, Metadata } from "../src/IPToken.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import { Safe } from "safe-global/safe-contracts/Safe.sol"; @@ -16,13 +16,13 @@ contract PermissionerTest is Test { address deployer = makeAddr("chucknorris"); address originalOwner = makeAddr("daoMultisig"); - //Alice, Bob are molecules holders + //Alice, Bob are ipToken holders address alice = makeAddr("alice"); uint256 alicePk; address bob = makeAddr("bob"); uint256 bobPk; - Molecules molecules; + IPToken ipToken; TermsAcceptedPermissioner internal permissioner; function setUp() public { @@ -31,9 +31,9 @@ contract PermissionerTest is Test { vm.startPrank(deployer); permissioner = new TermsAcceptedPermissioner(); - molecules = new Molecules(); + ipToken = new IPToken(); Metadata memory md = Metadata(1, originalOwner, "abcde"); - molecules.initialize("foo", "BAR", md); + ipToken.initialize("foo", "BAR", md); vm.stopPrank(); } @@ -41,18 +41,18 @@ contract PermissionerTest is Test { function testProveSigAndAcceptTerms() public { vm.startPrank(originalOwner); - string memory terms = permissioner.specificTermsV1(molecules); + string memory terms = permissioner.specificTermsV1(ipToken); (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, ECDSA.toEthSignedMessageHash(abi.encodePacked(terms))); bytes memory xsignature = abi.encodePacked(r, s, v); - assertTrue(permissioner.isValidSignature(molecules, alice, xsignature)); + assertTrue(permissioner.isValidSignature(ipToken, alice, xsignature)); vm.expectRevert(InvalidSignature.selector); - permissioner.accept(molecules, originalOwner, xsignature); + permissioner.accept(ipToken, originalOwner, xsignature); vm.stopPrank(); vm.startPrank(alice); - permissioner.accept(molecules, alice, xsignature); + permissioner.accept(ipToken, alice, xsignature); vm.stopPrank(); } @@ -70,7 +70,7 @@ contract PermissionerTest is Test { CompatibilityFallbackHandler fallbackHandler = new CompatibilityFallbackHandler(); bytes32 messagehash = fallbackHandler.getMessageHashForSafe( - wallet, abi.encodePacked(ECDSA.toEthSignedMessageHash(abi.encodePacked(permissioner.specificTermsV1(molecules)))) + wallet, abi.encodePacked(ECDSA.toEthSignedMessageHash(abi.encodePacked(permissioner.specificTermsV1(ipToken)))) ); (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(alicePk, messagehash); @@ -78,10 +78,10 @@ contract PermissionerTest is Test { bytes memory signature = bytes.concat(abi.encodePacked(r1, s1, v1), abi.encodePacked(r2, s2, v2)); - assertTrue(permissioner.isValidSignature(molecules, address(wallet), signature)); + assertTrue(permissioner.isValidSignature(ipToken, address(wallet), signature)); vm.startPrank(alice); - permissioner.accept(molecules, address(wallet), signature); + permissioner.accept(ipToken, address(wallet), signature); vm.stopPrank(); } } diff --git a/test/SalesShareDistributor.t.sol b/test/SalesShareDistributor.t.sol index 712b4ebd..256f8f08 100644 --- a/test/SalesShareDistributor.t.sol +++ b/test/SalesShareDistributor.t.sol @@ -16,7 +16,7 @@ import { AcceptAllAuthorizer } from "./helpers/AcceptAllAuthorizer.sol"; import { IPermissioner, TermsAcceptedPermissioner, BlindPermissioner, InvalidSignature } from "../src/Permissioner.sol"; import { IPNFTMintHelper } from "./IPNFTMintHelper.sol"; -import { Synthesizer } from "../src/Synthesizer.sol"; +import { Tokenizer } from "../src/Tokenizer.sol"; import { SalesShareDistributor, @@ -28,12 +28,12 @@ import { InsufficientBalance } from "../src/SalesShareDistributor.sol"; -import { Molecules, OnlyIssuerOrOwner } from "../src/Molecules.sol"; +import { IPToken, OnlyIssuerOrOwner } from "../src/IPToken.sol"; import { SchmackoSwap, ListingState } from "../src/SchmackoSwap.sol"; import { FakeERC20 } from "../src/helpers/FakeERC20.sol"; contract SalesShareDistributorTest is Test { - using SafeERC20Upgradeable for Molecules; + using SafeERC20Upgradeable for IPToken; string ipfsUri = "ipfs://bafkreiankqd3jvpzso6khstnaoxovtyezyatxdy7t2qzjoolqhltmasqki"; string agreementCid = "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq"; @@ -54,7 +54,7 @@ contract SalesShareDistributorTest is Test { address escrow = makeAddr("escrow"); IPNFT internal ipnft; - Synthesizer internal synthesizer; + Tokenizer internal tokenizer; SalesShareDistributor internal distributor; SchmackoSwap internal schmackoSwap; @@ -75,17 +75,17 @@ contract SalesShareDistributorTest is Test { blindPermissioner = new BlindPermissioner(); - synthesizer = Synthesizer( + tokenizer = Tokenizer( address( new ERC1967Proxy( address( - new Synthesizer() + new Tokenizer() ), "" ) ) ); - synthesizer.initialize(ipnft, blindPermissioner); + tokenizer.initialize(ipnft, blindPermissioner); distributor = SalesShareDistributor( address( @@ -117,8 +117,8 @@ contract SalesShareDistributorTest is Test { function testCreateListingAndSell() public { vm.startPrank(originalOwner); - //ipnft.setApprovalForAll(address(synthesizer), true); - Molecules tokenContract = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + //ipnft.setApprovalForAll(address(tokenizer), true); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); uint256 listingId = helpCreateListing(1_000_000 ether, address(tokenContract)); vm.stopPrank(); @@ -142,8 +142,8 @@ contract SalesShareDistributorTest is Test { function testStartClaimingPhase() public { vm.startPrank(originalOwner); - // ipnft.setApprovalForAll(address(synthesizer), true); - Molecules tokenContract = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + // ipnft.setApprovalForAll(address(tokenizer), true); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); uint256 listingId = helpCreateListing(1_000_000 ether, address(distributor)); vm.stopPrank(); @@ -175,7 +175,7 @@ contract SalesShareDistributorTest is Test { function testManuallyStartClaimingPhase() public { vm.startPrank(originalOwner); - Molecules tokenContract = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); ipnft.safeTransferFrom(originalOwner, ipnftBuyer, 1); assertEq(tokenContract.issuer(), originalOwner); tokenContract.cap(); @@ -206,7 +206,7 @@ contract SalesShareDistributorTest is Test { function testClaimBuyoutShares() public { vm.startPrank(originalOwner); - Molecules tokenContract = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); tokenContract.cap(); TermsAcceptedPermissioner permissioner = new TermsAcceptedPermissioner(); @@ -275,7 +275,7 @@ contract SalesShareDistributorTest is Test { function testClaimBuyoutSharesAfterSwap() public { vm.startPrank(originalOwner); - Molecules tokenContract = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); tokenContract.cap(); uint256 listingId = helpCreateListing(1_000_000 ether, address(distributor)); @@ -315,16 +315,16 @@ contract SalesShareDistributorTest is Test { assertEq(remainingAmount, 0); } - function testFuzzSynthesize(uint256 moleculesAmount, uint256 salesPrice) public { - vm.assume(moleculesAmount <= 2 ** 200); + function testFuzzSynthesize(uint256 iptokenAmount, uint256 salesPrice) public { + vm.assume(iptokenAmount <= 2 ** 200); vm.assume(salesPrice <= 100_000_000_000 ether); vm.startPrank(originalOwner); - Molecules tokenContract = synthesizer.synthesizeIpnft(1, moleculesAmount, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, iptokenAmount, "MOLE", agreementCid, ""); tokenContract.cap(); - assertEq(tokenContract.balanceOf(originalOwner), moleculesAmount); - tokenContract.safeTransfer(alice, moleculesAmount); + assertEq(tokenContract.balanceOf(originalOwner), iptokenAmount); + tokenContract.safeTransfer(alice, iptokenAmount); vm.stopPrank(); vm.startPrank(ipnftBuyer); @@ -340,7 +340,7 @@ contract SalesShareDistributorTest is Test { function testClaimingFraud() public { vm.startPrank(originalOwner); - Molecules tokenContract1 = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract1 = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); tokenContract1.cap(); ipnft.setApprovalForAll(address(schmackoSwap), true); uint256 listingId1 = schmackoSwap.list(IERC721(address(ipnft)), 1, erc20, 1000 ether, address(distributor)); @@ -356,7 +356,7 @@ contract SalesShareDistributorTest is Test { uint256 reservationId = ipnft.reserve(); ipnft.mintReservation{ value: MINTING_FEE }(bob, reservationId, ipfsUri, DEFAULT_SYMBOL, ""); ipnft.setApprovalForAll(address(schmackoSwap), true); - Molecules tokenContract2 = synthesizer.synthesizeIpnft(2, 70_000, "MOLE", agreementCid, ""); + IPToken tokenContract2 = tokenizer.tokenizeIpnft(2, 70_000, "MOLE", agreementCid, ""); tokenContract2.cap(); uint256 listingId2 = schmackoSwap.list(IERC721(address(ipnft)), 2, erc20, 700 ether, address(originalOwner)); schmackoSwap.changeBuyerAllowance(listingId2, ipnftBuyer, true); diff --git a/test/SynthesizerUpgrade.t.sol b/test/SynthesizerUpgrade.t.sol new file mode 100644 index 00000000..ff7fbec7 --- /dev/null +++ b/test/SynthesizerUpgrade.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; + +import { Safe } from "safe-global/safe-contracts/Safe.sol"; +import { SafeProxyFactory } from "safe-global/safe-contracts/proxies/SafeProxyFactory.sol"; +import { Enum } from "safe-global/safe-contracts/common/Enum.sol"; +import "./helpers/MakeGnosisWallet.sol"; +import { IPNFT } from "../src/IPNFT.sol"; +import { AcceptAllAuthorizer } from "./helpers/AcceptAllAuthorizer.sol"; + +import { FakeERC20 } from "../src/helpers/FakeERC20.sol"; +import { MustOwnIpnft, AlreadyTokenized, Tokenizer } from "../src/Tokenizer.sol"; + +import { IPToken, Metadata as IPTMetadata, OnlyIssuerOrOwner, TokenCapped } from "../src/IPToken.sol"; +import { Molecules, Metadata as MoleculeMetadata } from "../src/helpers/test-upgrades/Molecules.sol"; +import { Synthesizer } from "../src/helpers/test-upgrades/Synthesizer.sol"; +import { IPermissioner, TermsAcceptedPermissioner } from "../src/Permissioner.sol"; +import { + IPermissioner as OldIPermissioner, + TermsAcceptedPermissioner as OldTermsAcceptedPermissioner +} from "../src/helpers/test-upgrades/SynthPermissioner.sol"; + +import { SchmackoSwap, ListingState } from "../src/SchmackoSwap.sol"; + +contract SynthesizerUpgradeTest is Test { + using SafeERC20Upgradeable for IPToken; + + string ipfsUri = "ipfs://bafkreiankqd3jvpzso6khstnaoxovtyezyatxdy7t2qzjoolqhltmasqki"; + string agreementCid = "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq"; + uint256 MINTING_FEE = 0.001 ether; + string DEFAULT_SYMBOL = "MOL-0001"; + + address deployer = makeAddr("chucknorris"); + address protocolOwner = makeAddr("protocolOwner"); + address originalOwner = makeAddr("daoMultisig"); + uint256 originalOwnerPk; + + //Alice, Bob and Charlie are molecules holders + address alice = makeAddr("alice"); + uint256 alicePk; + + address bob = makeAddr("bob"); + uint256 bobPk; + address charlie = makeAddr("charlie"); + address escrow = makeAddr("escrow"); + + IPNFT internal ipnft; + Synthesizer internal synthesizer; + OldTermsAcceptedPermissioner internal oldTermsPermissioner; + + FakeERC20 internal erc20; + + function setUp() public { + (alice, alicePk) = makeAddrAndKey("alice"); + (bob, bobPk) = makeAddrAndKey("bob"); + (originalOwner, originalOwnerPk) = makeAddrAndKey("daoMultisig"); + + vm.startPrank(deployer); + + ipnft = IPNFT(address(new ERC1967Proxy(address(new IPNFT()), ""))); + ipnft.initialize(); + ipnft.setAuthorizer(new AcceptAllAuthorizer()); + + erc20 = new FakeERC20('Fake ERC20', 'FERC'); + //erc20.mint(ipnftBuyer, 1_000_000 ether); + + oldTermsPermissioner = new OldTermsAcceptedPermissioner(); + + synthesizer = Synthesizer(address(new ERC1967Proxy(address(new Synthesizer()), ""))); + synthesizer.initialize(ipnft, oldTermsPermissioner); + + vm.stopPrank(); + + vm.deal(originalOwner, MINTING_FEE); + vm.startPrank(originalOwner); + uint256 reservationId = ipnft.reserve(); + ipnft.mintReservation{ value: MINTING_FEE }(originalOwner, reservationId, ipfsUri, DEFAULT_SYMBOL, ""); + vm.stopPrank(); + } + + function testCanUpgradeErc20TokenImplementation() public { + vm.startPrank(deployer); + vm.stopPrank(); + + vm.startPrank(originalOwner); + MoleculeMetadata memory oldMetadata = MoleculeMetadata(1, originalOwner, agreementCid); + string memory terms = oldTermsPermissioner.specificTermsV1(oldMetadata); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(originalOwnerPk, ECDSA.toEthSignedMessageHash(abi.encodePacked(terms))); + bytes memory xsignature = abi.encodePacked(r, s, v); + + Molecules tokenContractOld = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, xsignature); + vm.stopPrank(); + + vm.startPrank(deployer); + Tokenizer tokenizerImpl = new Tokenizer(); + + synthesizer.upgradeTo(address(tokenizerImpl)); + + Tokenizer tokenizer = Tokenizer(address(synthesizer)); + + TermsAcceptedPermissioner termsPermissioner = new TermsAcceptedPermissioner(); + tokenizer.reinit(termsPermissioner); + vm.stopPrank(); + + assertEq(tokenContractOld.balanceOf(originalOwner), 100_000); + + vm.deal(originalOwner, MINTING_FEE); + vm.startPrank(originalOwner); + uint256 reservationId = ipnft.reserve(); + ipnft.mintReservation{ value: MINTING_FEE }(originalOwner, reservationId, ipfsUri, DEFAULT_SYMBOL, ""); + IPTMetadata memory newMetadata = IPTMetadata(2, originalOwner, agreementCid); + + terms = termsPermissioner.specificTermsV1(newMetadata); + (v, r, s) = vm.sign(originalOwnerPk, ECDSA.toEthSignedMessageHash(abi.encodePacked(terms))); + xsignature = abi.encodePacked(r, s, v); + IPToken tokenContractNew = tokenizer.tokenizeIpnft(2, 70_000, "FAST", agreementCid, xsignature); + vm.stopPrank(); + + assertEq(tokenContractNew.balanceOf(originalOwner), 70_000); + + vm.startPrank(originalOwner); + tokenContractOld.issue(originalOwner, 50_000); + assertEq(tokenContractOld.balanceOf(originalOwner), 150_000); + + tokenContractNew.issue(originalOwner, 30_000); + assertEq(tokenContractNew.balanceOf(originalOwner), 100_000); + vm.stopPrank(); + } +} diff --git a/test/Synthesizer.t.sol b/test/Tokenizer.t.sol similarity index 66% rename from test/Synthesizer.t.sol rename to test/Tokenizer.t.sol index cd6040bb..4a1a0179 100644 --- a/test/Synthesizer.t.sol +++ b/test/Tokenizer.t.sol @@ -17,17 +17,17 @@ import { IPNFT } from "../src/IPNFT.sol"; import { AcceptAllAuthorizer } from "./helpers/AcceptAllAuthorizer.sol"; import { FakeERC20 } from "../src/helpers/FakeERC20.sol"; -import { Synthesizer } from "../src/Synthesizer.sol"; -import { MustOwnIpnft, AlreadySynthesized } from "../src/Synthesizer.sol"; +import { MustOwnIpnft, AlreadyTokenized, Tokenizer } from "../src/Tokenizer.sol"; -import { Molecules, OnlyIssuerOrOwner, TokenCapped } from "../src/Molecules.sol"; -import { SynthesizerNext, MoleculesNext } from "../src/helpers/test-upgrades/SynthesizerNext.sol"; +import { IPToken, OnlyIssuerOrOwner, TokenCapped } from "../src/IPToken.sol"; +import { Molecules } from "../src/helpers/test-upgrades/Molecules.sol"; +import { Synthesizer } from "../src/helpers/test-upgrades/Synthesizer.sol"; import { IPermissioner, BlindPermissioner } from "../src/Permissioner.sol"; import { SchmackoSwap, ListingState } from "../src/SchmackoSwap.sol"; -contract SynthesizerTest is Test { - using SafeERC20Upgradeable for Molecules; +contract TokenizerTest is Test { + using SafeERC20Upgradeable for IPToken; string ipfsUri = "ipfs://bafkreiankqd3jvpzso6khstnaoxovtyezyatxdy7t2qzjoolqhltmasqki"; string agreementCid = "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq"; @@ -49,7 +49,7 @@ contract SynthesizerTest is Test { address escrow = makeAddr("escrow"); IPNFT internal ipnft; - Synthesizer internal synthesizer; + Tokenizer internal tokenizer; SchmackoSwap internal schmackoSwap; IPermissioner internal blindPermissioner; @@ -70,8 +70,8 @@ contract SynthesizerTest is Test { blindPermissioner = new BlindPermissioner(); - synthesizer = Synthesizer(address(new ERC1967Proxy(address(new Synthesizer()), ""))); - synthesizer.initialize(ipnft, blindPermissioner); + tokenizer = Tokenizer(address(new ERC1967Proxy(address(new Tokenizer()), ""))); + tokenizer.initialize(ipnft, blindPermissioner); vm.stopPrank(); @@ -84,16 +84,16 @@ contract SynthesizerTest is Test { function testUrl() public { vm.startPrank(originalOwner); - Molecules tokenContract = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); string memory uri = tokenContract.uri(); assertGt(bytes(uri).length, 200); vm.stopPrank(); } - function testIssueMolecules() public { + function testIssueIPToken() public { vm.startPrank(originalOwner); - //ipnft.setApprovalForAll(address(synthesizer), true); - Molecules tokenContract = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + //ipnft.setApprovalForAll(address(tokenizer), true); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); vm.stopPrank(); assertEq(tokenContract.balanceOf(originalOwner), 100_000); @@ -112,9 +112,9 @@ contract SynthesizerTest is Test { assertEq(tokenContract.totalSupply(), 100_000); } - function testIncreaseMolecules() public { + function testIncreaseIPToken() public { vm.startPrank(originalOwner); - Molecules tokenContract = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); tokenContract.transfer(alice, 25_000); tokenContract.transfer(bob, 25_000); @@ -145,24 +145,24 @@ contract SynthesizerTest is Test { vm.stopPrank(); } - function testCanBeSynthesizedOnlyOnce() public { + function testCanBeTokenizedOnlyOnce() public { vm.startPrank(originalOwner); - synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); - vm.expectRevert(AlreadySynthesized.selector); - synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + vm.expectRevert(AlreadyTokenized.selector); + tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); vm.stopPrank(); } - function testCannotSynthesizeIfNotOwner() public { + function testCannotTokenizeIfNotOwner() public { vm.startPrank(alice); vm.expectRevert(MustOwnIpnft.selector); - synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); vm.stopPrank(); } - function testGnosisSafeCanInteractWithMolecules() public { + function testGnosisSafeCanInteractWithIPToken() public { vm.startPrank(deployer); SafeProxyFactory fac = new SafeProxyFactory(); vm.stopPrank(); @@ -174,7 +174,7 @@ contract SynthesizerTest is Test { vm.stopPrank(); vm.startPrank(originalOwner); - Molecules tokenContract = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); tokenContract.safeTransfer(address(wallet), 100_000); vm.stopPrank(); @@ -198,41 +198,41 @@ contract SynthesizerTest is Test { assertEq(tokenContract.balanceOf(bob), 10_000); } - function testCanUpgradeErc20TokenImplementation() public { - vm.startPrank(deployer); - vm.stopPrank(); + // function testCanUpgradeErc20TokenImplementation() public { + // vm.startPrank(deployer); + // vm.stopPrank(); - vm.startPrank(originalOwner); - Molecules tokenContractOld = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, ""); - vm.stopPrank(); + // vm.startPrank(originalOwner); + // IPToken tokenContractOld = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + // vm.stopPrank(); - vm.startPrank(deployer); - SynthesizerNext synthNext = new SynthesizerNext(); - synthesizer.upgradeTo(address(synthNext)); - SynthesizerNext synth2 = SynthesizerNext(address(synthesizer)); + // vm.startPrank(deployer); + // TokenizerNext synthNext = new TokenizerNext(); + // tokenizer.upgradeTo(address(synthNext)); + // TokenizerNext synth2 = TokenizerNext(address(tokenizer)); - IPermissioner _permissioner = new BlindPermissioner(); - synth2.reinit(_permissioner); - vm.stopPrank(); + // IPermissioner _permissioner = new BlindPermissioner(); + // synth2.reinit(_permissioner); + // vm.stopPrank(); - assertEq(tokenContractOld.balanceOf(originalOwner), 100_000); + // assertEq(tokenContractOld.balanceOf(originalOwner), 100_000); - vm.deal(originalOwner, MINTING_FEE); - vm.startPrank(originalOwner); - uint256 reservationId = ipnft.reserve(); - ipnft.mintReservation{ value: MINTING_FEE }(originalOwner, reservationId, ipfsUri, DEFAULT_SYMBOL, ""); - Molecules tokenContractNew = synth2.synthesizeIpnft(2, 70_000, agreementCid, ""); - vm.stopPrank(); + // vm.deal(originalOwner, MINTING_FEE); + // vm.startPrank(originalOwner); + // uint256 reservationId = ipnft.reserve(); + // ipnft.mintReservation{ value: MINTING_FEE }(originalOwner, reservationId, ipfsUri, DEFAULT_SYMBOL, ""); + // IPToken tokenContractNew = synth2.tokenizeIpnft(2, 70_000, agreementCid, ""); + // vm.stopPrank(); - assertEq(tokenContractNew.balanceOf(originalOwner), 70_000); + // assertEq(tokenContractNew.balanceOf(originalOwner), 70_000); - MoleculesNext newTokenImpl = MoleculesNext(address(tokenContractNew)); + // IPTokenNext newTokenImpl = IPTokenNext(address(tokenContractNew)); - newTokenImpl.setAStateVar(42); - assertEq(newTokenImpl.aNewStateVar(), 42); + // newTokenImpl.setAStateVar(42); + // assertEq(newTokenImpl.aNewStateVar(), 42); - MoleculesNext oldTokenImplWrapped = MoleculesNext(address(tokenContractOld)); - vm.expectRevert(); - oldTokenImplWrapped.setAStateVar(42); - } + // IPTokenNext oldTokenImplWrapped = IPTokenNext(address(tokenContractOld)); + // vm.expectRevert(); + // oldTokenImplWrapped.setAStateVar(42); + // } } From 84edd81ccb09bdcb9321af1147f68b578188936e Mon Sep 17 00:00:00 2001 From: Stefan Adolf Date: Mon, 17 Jul 2023 13:30:53 +0200 Subject: [PATCH 2/6] forge install: solidity-base64 --- .gitmodules | 3 +++ lib/solidity-base64 | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/solidity-base64 diff --git a/.gitmodules b/.gitmodules index e5dc25cf..d77c8ff1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,3 +24,6 @@ [submodule "lib/token-vesting-contract"] path = lib/token-vesting-contract url = https://github.com/moleculeprotocol/token-vesting-contract +[submodule "lib/solidity-base64"] + path = lib/solidity-base64 + url = https://github.com/hir0min/solidity-base64 diff --git a/lib/solidity-base64 b/lib/solidity-base64 new file mode 160000 index 00000000..53709598 --- /dev/null +++ b/lib/solidity-base64 @@ -0,0 +1 @@ +Subproject commit 537095980643f59e9e00b7b76d341bb9da438eaf From 0e56fcff7058657ecc0cac27cfe4faa02b81454d Mon Sep 17 00:00:00 2001 From: Stefan Adolf Date: Mon, 17 Jul 2023 14:53:54 +0200 Subject: [PATCH 3/6] verifies that upgraded metadata is still working Signed-off-by: Stefan Adolf Tokenizer: leaves storage slot name as "synthesized" adds a real life test case for upgrades during a running claiming phase Signed-off-by: Stefan Adolf renames Tokenizer in scripts Signed-off-by: Stefan Adolf --- .env.example | 6 +- remappings.txt | 1 + script/dev/CrowdSale.s.sol | 12 +-- script/dev/Tokenizer.s.sol | 8 +- setupLocal.sh | 4 +- src/Tokenizer.sol | 7 +- test/IPNFT.t.js | 2 +- test/SynthesizerUpgrade.t.sol | 147 ++++++++++++++++++++++++++++++---- test/helpers/Strings.sol | 13 +++ 9 files changed, 167 insertions(+), 33 deletions(-) create mode 100644 test/helpers/Strings.sol diff --git a/.env.example b/.env.example index 3edc2dc6..fe01d51f 100644 --- a/.env.example +++ b/.env.example @@ -23,14 +23,14 @@ VDAO_TOKEN_ADDRESS=0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6 PRICEFEED_ADDRESS=0x8A791620dd6260079BF849Dc5567aDC3F2FdC318 TERMS_ACCEPTED_PERMISSIONER_ADDRESS=0x610178dA211FEF7D417bC0e6FeD39F05609AD788 -SYNTHESIZER_ADDRESS=0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0 +TOKENIZER_ADDRESS=0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0 STAKED_LOCKING_CROWDSALE_ADDRESS=0x9A676e781A523b5d0C0e43731313A708CB607508 USDC6_ADDRESS=0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE WETH_ADDRESS=0x59b670e9fA9D0A427751Af201D676719a970857b #these are generated when running the fixture scripts -MOLECULES_ADDRESS=0x1F708C24a0D3A740cD47cC0444E9480899f3dA7D -LOCKED_MOLECULES_ADDRESS=0x06cd7788D77332cF1156f1E327eBC090B5FF16a3 +IPTS_ADDRESS=0x1F708C24a0D3A740cD47cC0444E9480899f3dA7D +LOCKED_IPTS_ADDRESS=0x06cd7788D77332cF1156f1E327eBC090B5FF16a3 diff --git a/remappings.txt b/remappings.txt index 817fdedf..abe683f2 100644 --- a/remappings.txt +++ b/remappings.txt @@ -7,3 +7,4 @@ forge-std/=lib/forge-std/src/ solmate/=lib/solmate/src/ erc721b/=lib/ERC721B/contracts/ safe-global/safe-contracts/=lib/safe-contracts/contracts/ +base64/=lib/solidity-base64/contracts/libraries/ \ No newline at end of file diff --git a/script/dev/CrowdSale.s.sol b/script/dev/CrowdSale.s.sol index 92dba281..2d5a0520 100644 --- a/script/dev/CrowdSale.s.sol +++ b/script/dev/CrowdSale.s.sol @@ -6,7 +6,6 @@ import "forge-std/console.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -40,7 +39,7 @@ contract DeployCrowdSale is CommonScript { } /** - * @notice execute Ipnft.s.sol && Fixture.s.sol && Synthesize.s.sol first + * @notice execute Ipnft.s.sol && Fixture.s.sol && Tokenizer.s.sol first * @notice assumes that bob (hh1) owns IPNFT#1 and has synthesized it */ contract FixtureCrowdSale is CommonScript { @@ -61,7 +60,7 @@ contract FixtureCrowdSale is CommonScript { daoToken = FakeERC20(vm.envAddress("DAO_TOKEN_ADDRESS")); vestedDaoToken = TokenVesting(vm.envAddress("VDAO_TOKEN_ADDRESS")); - auctionToken = IPToken(vm.envAddress("MOLECULES_ADDRESS")); + auctionToken = IPToken(vm.envAddress("IPTS_ADDRESS")); stakedLockingCrowdSale = StakedLockingCrowdSale(vm.envAddress("STAKED_LOCKING_CROWDSALE_ADDRESS")); permissioner = TermsAcceptedPermissioner(vm.envAddress("TERMS_ACCEPTED_PERMISSIONER_ADDRESS")); @@ -69,7 +68,7 @@ contract FixtureCrowdSale is CommonScript { function setupVestedMolToken() internal { vm.startBroadcast(deployer); - auctionToken = IPToken(vm.envAddress("MOLECULES_ADDRESS")); + auctionToken = IPToken(vm.envAddress("IPTS_ADDRESS")); vestedDaoToken.grantRole(vestedDaoToken.ROLE_CREATE_SCHEDULE(), address(stakedLockingCrowdSale)); vm.stopBroadcast(); @@ -107,6 +106,7 @@ contract FixtureCrowdSale is CommonScript { }); vm.startBroadcast(bob); + auctionToken.approve(address(stakedLockingCrowdSale), 400 ether); uint256 saleId = stakedLockingCrowdSale.startSale(_sale, daoToken, vestedDaoToken, 1e18, 7 days); TimelockedToken lockedMolToken = stakedLockingCrowdSale.lockingContracts(address(auctionToken)); @@ -118,7 +118,7 @@ contract FixtureCrowdSale is CommonScript { placeBid(alice, 600 ether, saleId, abi.encodePacked(r, s, v)); (v, r, s) = vm.sign(charliePk, ECDSA.toEthSignedMessageHash(abi.encodePacked(terms))); placeBid(charlie, 200 ether, saleId, abi.encodePacked(r, s, v)); - console.log("LOCKED_MOLECULES_ADDRESS=%s", address(lockedMolToken)); + console.log("LOCKED_IPTS_ADDRESS=%s", address(lockedMolToken)); console.log("SALE_ID=%s", saleId); } } @@ -128,7 +128,7 @@ contract ClaimSale is CommonScript { prepareAddresses(); TermsAcceptedPermissioner permissioner = TermsAcceptedPermissioner(vm.envAddress("TERMS_ACCEPTED_PERMISSIONER_ADDRESS")); StakedLockingCrowdSale stakedLockingCrowdSale = StakedLockingCrowdSale(vm.envAddress("STAKED_LOCKING_CROWDSALE_ADDRESS")); - IPToken auctionToken = IPToken(vm.envAddress("MOLECULES_ADDRESS")); + IPToken auctionToken = IPToken(vm.envAddress("IPTS_ADDRESS")); uint256 saleId = vm.envUint("SALE_ID"); diff --git a/script/dev/Tokenizer.s.sol b/script/dev/Tokenizer.s.sol index d14a2c4b..46fc7ce2 100644 --- a/script/dev/Tokenizer.s.sol +++ b/script/dev/Tokenizer.s.sol @@ -26,7 +26,7 @@ contract DeployTokenizer is CommonScript { IPermissioner permissioner = IPermissioner(vm.envAddress("TERMS_ACCEPTED_PERMISSIONER_ADDRESS")); tokenizer.initialize(IPNFT(vm.envAddress("IPNFT_ADDRESS")), permissioner); vm.stopBroadcast(); - console.log("SYNTHESIZER_ADDRESS=%s", address(tokenizer)); + console.log("TOKENIZER_ADDRESS=%s", address(tokenizer)); } } @@ -42,7 +42,7 @@ contract FixtureTokenizer is CommonScript { function prepareAddresses() internal override { super.prepareAddresses(); - tokenizer = Tokenizer(vm.envAddress("SYNTHESIZER_ADDRESS")); + tokenizer = Tokenizer(vm.envAddress("TOKENIZER_ADDRESS")); permissioner = TermsAcceptedPermissioner(vm.envAddress("TERMS_ACCEPTED_PERMISSIONER_ADDRESS")); } @@ -58,7 +58,7 @@ contract FixtureTokenizer is CommonScript { tokenizer.tokenizeIpnft(1, 1_000_000 ether, "MOLE", "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq", signedTerms); vm.stopBroadcast(); - console.log("MOLECULES_ADDRESS=%s", address(tokenContract)); - console.log("molecules hash: %s", tokenContract.hash()); + console.log("IPTS_ADDRESS=%s", address(tokenContract)); + console.log("IPT round hash: %s", tokenContract.hash()); } } diff --git a/setupLocal.sh b/setupLocal.sh index 520b4c1a..62c814bb 100755 --- a/setupLocal.sh +++ b/setupLocal.sh @@ -27,7 +27,7 @@ done forge script script/dev/Ipnft.s.sol:DeployIpnftSuite -f $RPC_URL --broadcast forge script script/dev/Tokens.s.sol:DeployTokens -f $RPC_URL --broadcast forge script script/dev/Periphery.s.sol -f $RPC_URL --broadcast -forge script script/dev/Synthesizer.s.sol:DeploySynthesizer -f $RPC_URL --broadcast +forge script script/dev/Tokenizer.s.sol:DeployTokenizer -f $RPC_URL --broadcast forge script script/dev/CrowdSale.s.sol:DeployCrowdSale -f $RPC_URL --broadcast forge script script/dev/Tokens.s.sol:DeployFakeTokens -f $RPC_URL --broadcast @@ -36,7 +36,7 @@ if [ "$fixture" -eq "1" ]; then echo "Running fixture scripts." forge script script/dev/Ipnft.s.sol:FixtureIpnft -f $RPC_URL --broadcast - forge script script/dev/Synthesizer.s.sol:FixtureSynthesizer -f $RPC_URL --broadcast + forge script script/dev/Tokenizer.s.sol:FixtureTokenizer -f $RPC_URL --broadcast forge script script/dev/CrowdSale.s.sol:FixtureCrowdSale -f $RPC_URL --broadcast echo "SALE_ID= forge script script/dev/CrowdSale.s.sol:ClaimSale -f $RPC_URL --broadcast" diff --git a/src/Tokenizer.sol b/src/Tokenizer.sol index b1705407..717ac92a 100644 --- a/src/Tokenizer.sol +++ b/src/Tokenizer.sol @@ -29,7 +29,8 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable { IPNFT internal ipnft; - mapping(uint256 => IPToken) public tokenized; + //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; @@ -85,11 +86,11 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable { uint256 tokenHash = token.hash(); // ensure we can only call this once per sales cycle - if (address(tokenized[tokenHash]) != address(0)) { + if (address(synthesized[tokenHash]) != address(0)) { revert AlreadyTokenized(); } - tokenized[tokenHash] = token; + synthesized[tokenHash] = token; //this has been called MoleculesCreated before emit TokensCreated(tokenHash, ipnftId, address(token), _msgSender(), tokenAmount, agreementCid, name, tokenSymbol); diff --git a/test/IPNFT.t.js b/test/IPNFT.t.js index f9a14c04..d09f41eb 100644 --- a/test/IPNFT.t.js +++ b/test/IPNFT.t.js @@ -58,7 +58,7 @@ describe('IPNFT fundamentals and upgrades', function () { const result = await upgrades.validateUpgrade( synth0.address, - await ethers.getContractFactory('SynthesizerNext'), + await ethers.getContractFactory('Tokenizer'), { kind: 'uups' } diff --git a/test/SynthesizerUpgrade.t.sol b/test/SynthesizerUpgrade.t.sol index ff7fbec7..f00e44a4 100644 --- a/test/SynthesizerUpgrade.t.sol +++ b/test/SynthesizerUpgrade.t.sol @@ -3,18 +3,21 @@ pragma solidity ^0.8.18; import "forge-std/Test.sol"; import { console } from "forge-std/console.sol"; +import { stdJson } from "forge-std/StdJson.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +import { Base64Url } from "base64/Base64Url.sol"; + +import { Strings } from "./helpers/Strings.sol"; -import { Safe } from "safe-global/safe-contracts/Safe.sol"; -import { SafeProxyFactory } from "safe-global/safe-contracts/proxies/SafeProxyFactory.sol"; -import { Enum } from "safe-global/safe-contracts/common/Enum.sol"; -import "./helpers/MakeGnosisWallet.sol"; import { IPNFT } from "../src/IPNFT.sol"; import { AcceptAllAuthorizer } from "./helpers/AcceptAllAuthorizer.sol"; +import { TimelockedToken, StillLocked } from "../src/TimelockedToken.sol"; import { FakeERC20 } from "../src/helpers/FakeERC20.sol"; import { MustOwnIpnft, AlreadyTokenized, Tokenizer } from "../src/Tokenizer.sol"; @@ -22,13 +25,32 @@ import { MustOwnIpnft, AlreadyTokenized, Tokenizer } from "../src/Tokenizer.sol" import { IPToken, Metadata as IPTMetadata, OnlyIssuerOrOwner, TokenCapped } from "../src/IPToken.sol"; import { Molecules, Metadata as MoleculeMetadata } from "../src/helpers/test-upgrades/Molecules.sol"; import { Synthesizer } from "../src/helpers/test-upgrades/Synthesizer.sol"; -import { IPermissioner, TermsAcceptedPermissioner } from "../src/Permissioner.sol"; +import { IPermissioner, TermsAcceptedPermissioner, InvalidSignature } from "../src/Permissioner.sol"; import { IPermissioner as OldIPermissioner, TermsAcceptedPermissioner as OldTermsAcceptedPermissioner } from "../src/helpers/test-upgrades/SynthPermissioner.sol"; -import { SchmackoSwap, ListingState } from "../src/SchmackoSwap.sol"; +import { CrowdSale, SaleState, Sale, SaleInfo } from "../src/crowdsale/CrowdSale.sol"; +import { LockingCrowdSale, InvalidDuration } from "../src/crowdsale/LockingCrowdSale.sol"; +import { CrowdSaleHelpers } from "./helpers/CrowdSaleHelpers.sol"; + +struct IPTMetadataProps { + string agreement_content; + address erc20_contract; + uint256 ipnft_id; + address original_owner; + string supply; +} + +struct IPTUriMetadata { + uint256 decimals; + string description; + string external_url; + string image; + string name; + IPTMetadataProps properties; +} contract SynthesizerUpgradeTest is Test { using SafeERC20Upgradeable for IPToken; @@ -43,14 +65,12 @@ contract SynthesizerUpgradeTest is Test { address originalOwner = makeAddr("daoMultisig"); uint256 originalOwnerPk; - //Alice, Bob and Charlie are molecules holders + //Alice, Bob will be token holders address alice = makeAddr("alice"); uint256 alicePk; address bob = makeAddr("bob"); uint256 bobPk; - address charlie = makeAddr("charlie"); - address escrow = makeAddr("escrow"); IPNFT internal ipnft; Synthesizer internal synthesizer; @@ -70,7 +90,8 @@ contract SynthesizerUpgradeTest is Test { ipnft.setAuthorizer(new AcceptAllAuthorizer()); erc20 = new FakeERC20('Fake ERC20', 'FERC'); - //erc20.mint(ipnftBuyer, 1_000_000 ether); + erc20.mint(alice, 500_000 ether); + erc20.mint(bob, 500_000 ether); oldTermsPermissioner = new OldTermsAcceptedPermissioner(); @@ -87,9 +108,6 @@ contract SynthesizerUpgradeTest is Test { } function testCanUpgradeErc20TokenImplementation() public { - vm.startPrank(deployer); - vm.stopPrank(); - vm.startPrank(originalOwner); MoleculeMetadata memory oldMetadata = MoleculeMetadata(1, originalOwner, agreementCid); string memory terms = oldTermsPermissioner.specificTermsV1(oldMetadata); @@ -97,7 +115,6 @@ contract SynthesizerUpgradeTest is Test { bytes memory xsignature = abi.encodePacked(r, s, v); Molecules tokenContractOld = synthesizer.synthesizeIpnft(1, 100_000, "MOLE", agreementCid, xsignature); - vm.stopPrank(); vm.startPrank(deployer); Tokenizer tokenizerImpl = new Tokenizer(); @@ -133,5 +150,107 @@ contract SynthesizerUpgradeTest is Test { tokenContractNew.issue(originalOwner, 30_000); assertEq(tokenContractNew.balanceOf(originalOwner), 100_000); vm.stopPrank(); + + string memory encodedUri = tokenContractOld.uri(); + IPTUriMetadata memory parsedMetadata = + abi.decode(vm.parseJson(string(Base64Url.decode(Strings.substring(encodedUri, 29, bytes(encodedUri).length)))), (IPTUriMetadata)); + assertEq(parsedMetadata.name, "Molecules of IPNFT #1"); + + encodedUri = tokenContractNew.uri(); + parsedMetadata = + abi.decode(vm.parseJson(string(Base64Url.decode(Strings.substring(encodedUri, 29, bytes(encodedUri).length)))), (IPTUriMetadata)); + assertEq(parsedMetadata.name, "IP Tokens of IPNFT #2"); + } + + function helpSignMessage(uint256 pk, string memory terms) internal returns (bytes memory signature) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, ECDSA.toEthSignedMessageHash(abi.encodePacked(terms))); + signature = abi.encodePacked(r, s, v); + } + /** + * this demonstrates that upgrading the VITA-FAST sale works + */ + + function testCanInteractWithUpgradedERC20sAfterCrowdsale() public { + vm.startPrank(originalOwner); + MoleculeMetadata memory oldMetadata = MoleculeMetadata(1, originalOwner, agreementCid); + bytes memory xsignature = helpSignMessage(originalOwnerPk, oldTermsPermissioner.specificTermsV1(oldMetadata)); + + Molecules tokenContractOld = synthesizer.synthesizeIpnft(1, 500_000 ether, "MOLE", agreementCid, xsignature); + + LockingCrowdSale crowdSale = new LockingCrowdSale(); + Sale memory _sale = CrowdSaleHelpers.makeSale(originalOwner, IERC20Metadata(address(tokenContractOld)), erc20); + //todo: in reality the sale has been initialized with the old interface that used the `Molecule` type + _sale.permissioner = IPermissioner(address(oldTermsPermissioner)); + + tokenContractOld.approve(address(crowdSale), 400_000 ether); + uint256 saleId = crowdSale.startSale(_sale, 60 days); + vm.stopPrank(); + + vm.startPrank(alice); + xsignature = helpSignMessage(alicePk, oldTermsPermissioner.specificTermsV1(oldMetadata)); + erc20.approve(address(crowdSale), 100_000 ether); + crowdSale.placeBid(saleId, 100_000 ether, xsignature); + vm.stopPrank(); + + vm.startPrank(bob); + xsignature = helpSignMessage(bobPk, oldTermsPermissioner.specificTermsV1(oldMetadata)); + erc20.approve(address(crowdSale), 100_000 ether); + crowdSale.placeBid(saleId, 100_000 ether, xsignature); + vm.stopPrank(); + + vm.warp(block.timestamp + 3 hours); + TimelockedToken lockedAuctionToken = crowdSale.lockingContracts(address(tokenContractOld)); + + //bob settles & claims before the upgrade + vm.startPrank(bob); + crowdSale.settle(saleId); + + vm.recordLogs(); + //bob remembered his signature: + crowdSale.claim(saleId, xsignature); + Vm.Log[] memory entries = vm.getRecordedLogs(); + //0: TermsAccepted, 1: Claimed, 2: ScheduleCreated + assertEq(entries[2].topics[0], keccak256("ScheduleCreated(bytes32,address,address,uint256,uint64)")); + bytes32 bobScheduleId = entries[2].topics[1]; + vm.stopPrank(); + + assertEq(lockedAuctionToken.balanceOf(bob), 200_000 ether); + + //upgrade after sale concluded + vm.startPrank(deployer); + Tokenizer tokenizerImpl = new Tokenizer(); + synthesizer.upgradeTo(address(tokenizerImpl)); + + Tokenizer tokenizer = Tokenizer(address(synthesizer)); + TermsAcceptedPermissioner termsPermissioner = new TermsAcceptedPermissioner(); + tokenizer.reinit(termsPermissioner); + vm.stopPrank(); + + vm.startPrank(alice); + + //alice crafts signatures with the **new** permissioner... + xsignature = helpSignMessage(alicePk, termsPermissioner.specificTermsV1(IPTMetadata(1, originalOwner, agreementCid))); + vm.expectRevert(InvalidSignature.selector); + crowdSale.claim(saleId, xsignature); + + //instead, alice *must remember* to use the old permissioner's terms: + xsignature = helpSignMessage(alicePk, oldTermsPermissioner.specificTermsV1(MoleculeMetadata(1, originalOwner, agreementCid))); + vm.recordLogs(); + crowdSale.claim(saleId, xsignature); + entries = vm.getRecordedLogs(); + + assertEq(entries[2].topics[0], keccak256("ScheduleCreated(bytes32,address,address,uint256,uint64)")); + bytes32 aliceScheduleId = entries[2].topics[1]; + vm.stopPrank(); + + //finally get the tokens out + vm.warp(block.timestamp + 60 days); + vm.startPrank(alice); + lockedAuctionToken.release(aliceScheduleId); + lockedAuctionToken.release(bobScheduleId); + vm.stopPrank(); + + assertEq(tokenContractOld.balanceOf(alice), 200_000 ether); + assertEq(tokenContractOld.balanceOf(bob), 200_000 ether); } } diff --git a/test/helpers/Strings.sol b/test/helpers/Strings.sol new file mode 100644 index 00000000..1382b0e1 --- /dev/null +++ b/test/helpers/Strings.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library Strings { + function substring(string memory str, uint256 startIndex, uint256 endIndex) internal pure returns (string memory) { + bytes memory strBytes = bytes(str); + bytes memory result = new bytes(endIndex-startIndex); + for (uint256 i = startIndex; i < endIndex; i++) { + result[i - startIndex] = strBytes[i]; + } + return string(result); + } +} From 08ba3bf533acbdf37128615dbfb35256986c7b28 Mon Sep 17 00:00:00 2001 From: Stefan Adolf Date: Mon, 17 Jul 2023 19:51:02 +0200 Subject: [PATCH 4/6] subgraph renaming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ReactedIPNFT -> IPT Molecules -> IPTBalance injects legacy molecule event to the Tokenizer ABI Signed-off-by: Stefan Adolf modifies setup script to test renaming upgrade (#138) * modifies setup script to test renaming upgrade * consolidates setup script & allows inline settling * uses fs to hand over the SALE_ID * slightly improved config for manual subgraph tests * indexes old permissioner contracts * adds sale instance permissioner address to subgraph * iptBalances pluralization * reduces local setup script & synth upgrades * updated the rollout script, adds action comments * lets IPNFT2.4 emit an event when Authorizer is updated * ensure the reinits can only be executed once * deployed new permissioner on görli * uses reinit level 4 for görli compatibility * adds new addresses / also to subgraph config --------- Signed-off-by: Stefan Adolf Signed-off-by: stadolf --- .env.upgrades | 21 +++ README.md | 34 ++-- foundry.toml | 2 +- script/dev/ApproveAndBuy.s.sol | 4 +- script/dev/CrowdSale.s.sol | 15 +- script/dev/SignTermsMessage.s.sol | 2 +- script/dev/Synthesizer.s.sol | 106 ++++++++++++ script/dev/Tokenizer.s.sol | 4 +- script/prod/RolloutV24.sol | 38 ++++- setupForUpgrades.sh | 28 ++++ setupLocal.sh | 25 +-- src/IPNFT.sol | 2 + src/Tokenizer.sol | 6 +- src/helpers/Strings.sol | 15 ++ .../abis/{Molecules.json => IPToken.json} | 0 subgraph/abis/SharedSalesDistributor.json | 8 +- subgraph/abis/TermsAcceptedPermissioner.json | 6 +- .../abis/{Synthesizer.json => Tokenizer.json} | 156 +++++++++++++----- subgraph/config/goerli.js | 6 +- subgraph/config/local.js | 6 +- subgraph/config/mainnet.js | 2 +- subgraph/makeAbis.sh | 65 +++++++- subgraph/schema.graphql | 29 ++-- subgraph/src/iptMapping.ts | 70 ++++++++ subgraph/src/moleculesMapping.ts | 76 --------- subgraph/src/stakedLockingCrowdSaleMapping.ts | 48 +++--- .../src/termsAcceptedPermissionerMapping.ts | 24 +-- subgraph/src/timelockedTokenMapping.ts | 26 +-- ...thesizerMapping.ts => tokenizerMapping.ts} | 15 +- subgraph/subgraph.template.yaml | 38 +++-- test/IPNFTUpgrades.t.sol | 7 +- test/SynthesizerUpgrade.t.sol | 4 + 32 files changed, 618 insertions(+), 270 deletions(-) create mode 100644 .env.upgrades create mode 100644 script/dev/Synthesizer.s.sol create mode 100755 setupForUpgrades.sh create mode 100644 src/helpers/Strings.sol rename subgraph/abis/{Molecules.json => IPToken.json} (100%) rename subgraph/abis/{Synthesizer.json => Tokenizer.json} (77%) create mode 100644 subgraph/src/iptMapping.ts delete mode 100644 subgraph/src/moleculesMapping.ts rename subgraph/src/{synthesizerMapping.ts => tokenizerMapping.ts} (78%) diff --git a/.env.upgrades b/.env.upgrades new file mode 100644 index 00000000..8762d18c --- /dev/null +++ b/.env.upgrades @@ -0,0 +1,21 @@ +RPC_URL="http://127.0.0.1:8545" +PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + +IPNFT_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +SOS_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 +AUTHORIZER_ADDRESS=0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 +USDC_ADDRESS=0x0165878A594ca255338adfa4d48449f69242Eb8F +DAO_TOKEN_ADDRESS=0xa513E6E4b8f2a923D98304ec87F64353C4D5C853 +VDAO_TOKEN_ADDRESS=0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6 +TERMS_ACCEPTED_PERMISSIONER_ADDRESS=0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e +SYNTHESIZER_ADDRESS=0x610178dA211FEF7D417bC0e6FeD39F05609AD788 +STAKED_LOCKING_CROWDSALE_ADDRESS=0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82 +USDC6_ADDRESS=0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1 +WETH_ADDRESS=0xc6e7DF5E7b4f2A278906862b61205850344D4e7d +TOKENIZER_ADDRESS=0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0 +IPTS_ADDRESS=0x6F1216D1BFe15c98520CA1434FC1d9D57AC95321 +LOCKED_IPTS_ADDRESS=0x4ABEaCA4b05d8fA4CED09D26aD28Ea298E8afaC8 + +## the new env vars (add to .env when testing upgrades): +# TOKENIZER_ADDRESS=0x610178dA211FEF7D417bC0e6FeD39F05609AD788 +# TERMS_ACCEPTED_PERMISSIONER_ADDRESS=0x9E545E3C0baAB3E08CdfD552C960A1050f373042 \ No newline at end of file diff --git a/README.md b/README.md index c98ad6e4..027176e5 100644 --- a/README.md +++ b/README.md @@ -18,36 +18,32 @@ IP-NFTs allow their users to tokenize intellectual property. This repo contains - Subgraph: synthImpl implementation 1: 0xb050A85933FF0807f05d289b7f6457c5eFbC348f -ipnft implementation: 0x0443DfAC8E510cFBDFdb9247E77400E9728aE45D +ipnft implementation 2.3: 0x0443DfAC8E510cFBDFdb9247E77400E9728aE45D --- ### Goerli -| Contract | Address | Actions | -| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| IP-NFT | [0xaf7358576C9F7cD84696D28702fC5ADe33cce0e9](https://goerli.etherscan.io/address/0xaf7358576C9F7cD84696D28702fC5ADe33cce0e9#code>) | View contract | -| SchmackoSwap | [0x67D8ed102E2168A46FA342e39A5f7D16c103Bd0d](https://goerli.etherscan.io/address/0x67D8ed102E2168A46FA342e39A5f7D16c103Bd0d#code) | View contract | -| Mintpass | [0xaf0f99dcc64e8a6549d32013ac9f2c3fa7834688](https://goerli.etherscan.io/address/0xaf0f99dcc64e8a6549d32013ac9f2c3fa7834688#code) | View contract | -| Synthesizer | [0xb12494eeA6B992d0A1Db3C5423BE7a2d2337F58c](https://goerli.etherscan.io/address/0xb12494eeA6B992d0A1Db3C5423BE7a2d2337F58c#code) | View contract | -| Permissioner | [0x0045723801561079d94f0Bb1B65f322078E52635](https://goerli.etherscan.io/address/0x0045723801561079d94f0Bb1B65f322078E52635#code) | View contract | -| SignedMintAuthorizer | [0x5e555eE24DB66825171Ac63EA614864987CEf1Af](https://goerli.etherscan.io/address/0x5e555eE24DB66825171Ac63EA614864987CEf1Af#code) | View contract | +| Contract | Address | Actions | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| IP-NFT | [0xaf7358576C9F7cD84696D28702fC5ADe33cce0e9](https://goerli.etherscan.io/address/0xaf7358576C9F7cD84696D28702fC5ADe33cce0e9#code>) | View contract | +| SchmackoSwap | [0x67D8ed102E2168A46FA342e39A5f7D16c103Bd0d](https://goerli.etherscan.io/address/0x67D8ed102E2168A46FA342e39A5f7D16c103Bd0d#code) | View contract | +| Mintpass | [0xaf0f99dcc64e8a6549d32013ac9f2c3fa7834688](https://goerli.etherscan.io/address/0xaf0f99dcc64e8a6549d32013ac9f2c3fa7834688#code) | View contract | +| Tokenizer | [0xb12494eeA6B992d0A1Db3C5423BE7a2d2337F58c](https://goerli.etherscan.io/address/0xb12494eeA6B992d0A1Db3C5423BE7a2d2337F58c#code) | View contract | +| Permissioner | [0xd735d9504cce32F2cd665b779D699B4157686fcd](https://goerli.etherscan.io/address/0xd735d9504cce32F2cd665b779D699B4157686fcd#code) | View contract | +| StakedLockingCrowdSale | [0x46c3369dece07176ad7164906d3593aa4c126d35](https://goerli.etherscan.io/address/0x46c3369dece07176ad7164906d3593aa4c126d35#code) | View contract | +| SignedMintAuthorizer | [0x5e555eE24DB66825171Ac63EA614864987CEf1Af](https://goerli.etherscan.io/address/0x5e555eE24DB66825171Ac63EA614864987CEf1Af#code) | View contract | - Subgraph: -- Synthesizer: 0xb12494eeA6B992d0A1Db3C5423BE7a2d2337F58c - (Impl no 5 0xc36888d56eD6782515CD4A259F2Aad708744Cc7d) - - - -- Terms Accepted Permissioner: 0x0045723801561079d94f0Bb1B65f322078E52635 - +- Tokenizer: 0xb12494eeA6B992d0A1Db3C5423BE7a2d2337F58c (formerly known as Synthesizer) + Implementation: 0x64580CC2c28aaEd31B2DF9a154dA9620C690BDec (reinit 4) - Bio pricefeed: 0x8647dEFdEAAdF5448d021B364B2F17815aba4360 -- Staked/Locking Crowdsale: 0x46c3369dece07176ad7164906d3593aa4c126d35 - +- old ("molecule") permissioner: 0x0045723801561079d94f0Bb1B65f322078E52635 + - Blind Permissioner: 0xec68a1fc8d4c2834f8dfbdb56691f9f0a3d6be11 @@ -284,6 +280,6 @@ The QueryIds and API-KEY are stored in the Tenderly context and can be accessed To update these actions you need the Tenderly login credentials. - StakedLockingCrowdSale (Mainnet & Goerli): BidEvent => Triggers a POST request that executes Dune Queries to update the Dune Visualizations. - + You can find out more about Web3Actions on Tenderly here: How to init & deploy new Web3Actions: diff --git a/foundry.toml b/foundry.toml index 8aae70c9..b5768bc4 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,7 +6,7 @@ test = 'test' cache_path = 'cache_forge' solc_version = "0.8.18" gas_reports = ["IPNFT", "IPNFTV2", "Mintpass", "SchmackoSwap", "Synthesizer", "Molecules", "CrowdSale", "LockingCrowdSale", "StakedLockingCrowdSale", "TimelockedToken"] - +fs_permissions = [{ access = "read-write", path = "./SALEID.txt"}] [fmt] bracket_spacing = true diff --git a/script/dev/ApproveAndBuy.s.sol b/script/dev/ApproveAndBuy.s.sol index 433f6c23..91bc28b5 100644 --- a/script/dev/ApproveAndBuy.s.sol +++ b/script/dev/ApproveAndBuy.s.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; import "forge-std/Script.sol"; import "forge-std/console.sol"; diff --git a/script/dev/CrowdSale.s.sol b/script/dev/CrowdSale.s.sol index 2d5a0520..0f367f13 100644 --- a/script/dev/CrowdSale.s.sol +++ b/script/dev/CrowdSale.s.sol @@ -15,6 +15,7 @@ import { IPermissioner, TermsAcceptedPermissioner } from "../../src/Permissioner import { CrowdSale, Sale, SaleInfo } from "../../src/crowdsale/CrowdSale.sol"; import { StakedLockingCrowdSale } from "../../src/crowdsale/StakedLockingCrowdSale.sol"; import { FakeERC20 } from "../../src/helpers/FakeERC20.sol"; +import { Strings as SLib } from "../../src/helpers/Strings.sol"; import { IPToken } from "../../src/IPToken.sol"; import { CommonScript } from "./Common.sol"; @@ -120,6 +121,7 @@ contract FixtureCrowdSale is CommonScript { placeBid(charlie, 200 ether, saleId, abi.encodePacked(r, s, v)); console.log("LOCKED_IPTS_ADDRESS=%s", address(lockedMolToken)); console.log("SALE_ID=%s", saleId); + vm.writeFile("SALEID.txt", Strings.toString(saleId)); } } @@ -129,8 +131,8 @@ contract ClaimSale is CommonScript { TermsAcceptedPermissioner permissioner = TermsAcceptedPermissioner(vm.envAddress("TERMS_ACCEPTED_PERMISSIONER_ADDRESS")); StakedLockingCrowdSale stakedLockingCrowdSale = StakedLockingCrowdSale(vm.envAddress("STAKED_LOCKING_CROWDSALE_ADDRESS")); IPToken auctionToken = IPToken(vm.envAddress("IPTS_ADDRESS")); - - uint256 saleId = vm.envUint("SALE_ID"); + uint256 saleId = SLib.stringToUint(vm.readFile("SALEID.txt")); + vm.removeFile("SALEID.txt"); vm.startBroadcast(anyone); stakedLockingCrowdSale.settle(saleId); @@ -144,9 +146,10 @@ contract ClaimSale is CommonScript { stakedLockingCrowdSale.claim(saleId, abi.encodePacked(r, s, v)); vm.stopBroadcast(); - vm.startBroadcast(charlie); - (v, r, s) = vm.sign(charliePk, ECDSA.toEthSignedMessageHash(abi.encodePacked(terms))); - stakedLockingCrowdSale.claim(saleId, abi.encodePacked(r, s, v)); - vm.stopBroadcast(); + //we don't let charlie claim so we can test upgrades + // vm.startBroadcast(charlie); + // (v, r, s) = vm.sign(charliePk, ECDSA.toEthSignedMessageHash(abi.encodePacked(terms))); + // stakedLockingCrowdSale.claim(saleId, abi.encodePacked(r, s, v)); + // vm.stopBroadcast(); } } diff --git a/script/dev/SignTermsMessage.s.sol b/script/dev/SignTermsMessage.s.sol index a8a78999..6251b891 100644 --- a/script/dev/SignTermsMessage.s.sol +++ b/script/dev/SignTermsMessage.s.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; +pragma solidity ^0.8.18; import "forge-std/Script.sol"; import "forge-std/console.sol"; diff --git a/script/dev/Synthesizer.s.sol b/script/dev/Synthesizer.s.sol new file mode 100644 index 00000000..0d57b939 --- /dev/null +++ b/script/dev/Synthesizer.s.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; + +import "forge-std/console.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import { TokenVesting } from "@moleculeprotocol/token-vesting/TokenVesting.sol"; + +import { IPNFT } from "../../src/IPNFT.sol"; +import { Tokenizer } from "../../src/Tokenizer.sol"; +import { Metadata, IPToken } from "../../src/IPToken.sol"; +import { IPermissioner, TermsAcceptedPermissioner } from "../../src/Permissioner.sol"; +import { StakedLockingCrowdSale } from "../../src/crowdsale/StakedLockingCrowdSale.sol"; + +import { FakeERC20 } from "../../src/helpers/FakeERC20.sol"; +import { Synthesizer } from "../../src/helpers/test-upgrades/Synthesizer.sol"; + +import { Metadata as MolMetadata, Molecules } from "../../src/helpers/test-upgrades/Molecules.sol"; +import { + IPermissioner as IMolPermissioner, + TermsAcceptedPermissioner as MolTermsAcceptedPermissioner +} from "../../src/helpers/test-upgrades/SynthPermissioner.sol"; + +import { CommonScript } from "./Common.sol"; + +/** + * @title DeploySynthesizer + * @notice only used for local testing. The "Synthesizer" is the old name for `Tokenizer`. + */ +contract DeploySynthesizer is CommonScript { + function run() public { + prepareAddresses(); + vm.startBroadcast(deployer); + Synthesizer synthesizer = Synthesizer( + address( + new ERC1967Proxy( + address(new Synthesizer()), "" + ) + ) + ); + MolTermsAcceptedPermissioner oldPermissioner = new MolTermsAcceptedPermissioner(); + + synthesizer.initialize(IPNFT(vm.envAddress("IPNFT_ADDRESS")), oldPermissioner); + vm.stopBroadcast(); + console.log("TERMS_ACCEPTED_PERMISSIONER_ADDRESS=%s", address(oldPermissioner)); + console.log("SYNTHESIZER_ADDRESS=%s", address(synthesizer)); + } +} + +/** + * @title FixtureSynthesizer + * @author + * @notice execute Ipnft.s.sol && DeploySynthesizer first + * @notice assumes that bob (hh1) owns IPNFT#1 + */ +contract FixtureSynthesizer is CommonScript { + Synthesizer synthesizer; + MolTermsAcceptedPermissioner oldPermissioner; + + function prepareAddresses() internal override { + super.prepareAddresses(); + synthesizer = Synthesizer(vm.envAddress("SYNTHESIZER_ADDRESS")); + oldPermissioner = MolTermsAcceptedPermissioner(vm.envAddress("TERMS_ACCEPTED_PERMISSIONER_ADDRESS")); + } + + function run() public { + prepareAddresses(); + + string memory terms = oldPermissioner.specificTermsV1(MolMetadata(1, bob, "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq")); + + vm.startBroadcast(bob); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(bobPk, ECDSA.toEthSignedMessageHash(abi.encodePacked(terms))); + bytes memory signedTerms = abi.encodePacked(r, s, v); + Molecules tokenContract = + synthesizer.synthesizeIpnft(1, 1_000_000 ether, "MOLE", "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq", signedTerms); + vm.stopBroadcast(); + + console.log("IPTS_ADDRESS=%s", address(tokenContract)); + console.log("ipts (molecules) round hash: %s", tokenContract.hash()); + } +} + +/** + * @notice allows testing contract upgrades on the frontend in a controlled way + */ +contract UpgradeSynthesizerToTokenizer is CommonScript { + function run() public { + prepareAddresses(); + Synthesizer synthesizer = Synthesizer(vm.envAddress("SYNTHESIZER_ADDRESS")); + + vm.startBroadcast(deployer); + Tokenizer tokenizerImpl = new Tokenizer(); + synthesizer.upgradeTo(address(tokenizerImpl)); + Tokenizer tokenizer = Tokenizer(address(synthesizer)); + + TermsAcceptedPermissioner newTermsPermissioner = new TermsAcceptedPermissioner(); + tokenizer.reinit(newTermsPermissioner); + vm.stopBroadcast(); + + console.log("TOKENIZER_ADDRESS=%s", address(tokenizer)); //should equal synthesizer + console.log("NEW_TERMS_ACCEPTED_PERMISSIONER_ADDRESS=%s", address(newTermsPermissioner)); + } +} diff --git a/script/dev/Tokenizer.s.sol b/script/dev/Tokenizer.s.sol index 46fc7ce2..7d2bb92f 100644 --- a/script/dev/Tokenizer.s.sol +++ b/script/dev/Tokenizer.s.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; import "forge-std/Script.sol"; import "forge-std/console.sol"; diff --git a/script/prod/RolloutV24.sol b/script/prod/RolloutV24.sol index 2ee55e24..7d9a14f5 100644 --- a/script/prod/RolloutV24.sol +++ b/script/prod/RolloutV24.sol @@ -8,7 +8,13 @@ import { IPNFT } from "../../src/IPNFT.sol"; import { IPermissioner, TermsAcceptedPermissioner } from "../../src/Permissioner.sol"; import { StakedLockingCrowdSale } from "../../src/crowdsale/StakedLockingCrowdSale.sol"; import { SignedMintAuthorizer } from "../../src/SignedMintAuthorizer.sol"; +import { Tokenizer } from "../../src/Tokenizer.sol"; +/** + * @title Rollout24 + * @author molecule.to + * @notice deploys / initializes new contracts and implementations on mainnet + */ contract RolloutV24 is Script { function run() public { vm.startBroadcast(); @@ -17,9 +23,35 @@ contract RolloutV24 is Script { //address goerliDefenderRelayer = 0xbCeb6b875513629eFEDeF2A2D0b2f2a8fd2D4Ea4; address mainnetDefenderRelayer = 0x3D30452c48F2448764d5819a9A2b684Ae2CC5AcF; SignedMintAuthorizer authorizer = new SignedMintAuthorizer(mainnetDefenderRelayer); - + TermsAcceptedPermissioner permissioner = new TermsAcceptedPermissioner(); + Tokenizer tokenizerImpl = new Tokenizer(); vm.stopBroadcast(); - console.log("ipnft implementation: %s", address(ipnftImpl)); - console.log("Authorizer: %s", address(authorizer)); + + console.log("SIGNED_MINT_AUTHORIZER=%s", address(authorizer)); + console.log("TERMS_ACCEPTED_PERMISSIONER_ADDRESS=%s", address(permissioner)); + console.log("new ipnft impl: %s", address(ipnftImpl)); + console.log("new tokenizer impl: %s", address(tokenizerImpl)); } } + +/* + //multisig: Upgrade IPNFT to 2.4 / set new authorizer + // 0xcaD88677CA87a7815728C72D74B4ff4982d54Fc1 //the IPNFT proxy + ipnft.upgradeTo(address(ipnftImpl)) + //set new authorizer on current IPNFT contract: + ipnft.setAuthorizer(authorizer); +*/ + +/* + //upgrade Synthesizer -> Tokenizer using multisig + + //0x58EB89C69CB389DBef0c130C6296ee271b82f436 //that's the synthesizer mainnet proxy + synthesizer.upgradeTo(address(tokenizerImpl)); + + // now, it's a Tokenizer + Tokenizer tokenizer = Tokenizer(0x58EB89C69CB389DBef0c130C6296ee271b82f436); + tokenizer.reinit(newTermsPermissioner); + +> forge create --rpc-url $RPC_URL --private-key $PRIVATE_KEY src/Permissioner.sol:TermsAcceptedPermissioner --etherscan-api-key $ETHERSCAN_API_KEY --verify + +*/ diff --git a/setupForUpgrades.sh b/setupForUpgrades.sh new file mode 100755 index 00000000..412670d7 --- /dev/null +++ b/setupForUpgrades.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# use deterministic addresses +#source ./.env.upgrades +set -a + . ./.env.upgrades +set +a + +FSC="forge script -f $RPC_URL --broadcast --revert-strings debug" + +# Deployments +$FSC script/dev/Ipnft.s.sol:DeployIpnftSuite +$FSC script/dev/Tokens.s.sol:DeployTokens +$FSC script/dev/Synthesizer.s.sol:DeploySynthesizer +$FSC script/dev/CrowdSale.s.sol:DeployCrowdSale +$FSC script/dev/Tokens.s.sol:DeployFakeTokens + +$FSC script/dev/Ipnft.s.sol:FixtureIpnft +$FSC script/dev/Synthesizer.s.sol:FixtureSynthesizer +$FSC script/dev/CrowdSale.s.sol:FixtureCrowdSale + +echo "Waiting 15 seconds until claiming sale..." +sleep 16 +cast rpc evm_mine + +$FSC script/dev/CrowdSale.s.sol:ClaimSale +$FSC script/dev/Synthesizer.s.sol:UpgradeSynthesizerToTokenizer + diff --git a/setupLocal.sh b/setupLocal.sh index 62c814bb..aed1912f 100755 --- a/setupLocal.sh +++ b/setupLocal.sh @@ -22,22 +22,27 @@ while [ "$#" -gt 0 ]; do shift done +FSC="forge script -f $RPC_URL --broadcast --revert-strings debug" # Deployments -forge script script/dev/Ipnft.s.sol:DeployIpnftSuite -f $RPC_URL --broadcast -forge script script/dev/Tokens.s.sol:DeployTokens -f $RPC_URL --broadcast -forge script script/dev/Periphery.s.sol -f $RPC_URL --broadcast -forge script script/dev/Tokenizer.s.sol:DeployTokenizer -f $RPC_URL --broadcast -forge script script/dev/CrowdSale.s.sol:DeployCrowdSale -f $RPC_URL --broadcast -forge script script/dev/Tokens.s.sol:DeployFakeTokens -f $RPC_URL --broadcast +$FSC script/dev/Ipnft.s.sol:DeployIpnftSuite +$FSC script/dev/Tokens.s.sol:DeployTokens +$FSC script/dev/Periphery.s.sol +$FSC script/dev/Tokenizer.s.sol:DeployTokenizer +$FSC script/dev/CrowdSale.s.sol:DeployCrowdSale +$FSC script/dev/Tokens.s.sol:DeployFakeTokens # optionally: fixtures if [ "$fixture" -eq "1" ]; then echo "Running fixture scripts." - forge script script/dev/Ipnft.s.sol:FixtureIpnft -f $RPC_URL --broadcast - forge script script/dev/Tokenizer.s.sol:FixtureTokenizer -f $RPC_URL --broadcast - forge script script/dev/CrowdSale.s.sol:FixtureCrowdSale -f $RPC_URL --broadcast + $FSC script/dev/Ipnft.s.sol:FixtureIpnft + $FSC script/dev/Tokenizer.s.sol:FixtureTokenizer + $FSC script/dev/CrowdSale.s.sol:FixtureCrowdSale - echo "SALE_ID= forge script script/dev/CrowdSale.s.sol:ClaimSale -f $RPC_URL --broadcast" + echo "Waiting 15 seconds until claiming sale..." + sleep 16 + cast rpc evm_mine + + $FSC script/dev/CrowdSale.s.sol:ClaimSale fi diff --git a/src/IPNFT.sol b/src/IPNFT.sol index dfa701f9..597869e3 100644 --- a/src/IPNFT.sol +++ b/src/IPNFT.sol @@ -47,6 +47,7 @@ contract IPNFT is ERC721URIStorageUpgradeable, ERC721BurnableUpgradeable, IReser event Reserved(address indexed reserver, uint256 indexed reservationId); event IPNFTMinted(address indexed owner, uint256 indexed tokenId, string tokenURI, string symbol); event ReadAccessGranted(uint256 indexed tokenId, address indexed reader, uint256 until); + event AuthorizerUpdated(address authorizer); error NotOwningReservation(uint256 id); error ToZeroAddress(); @@ -79,6 +80,7 @@ contract IPNFT is ERC721URIStorageUpgradeable, ERC721BurnableUpgradeable, IReser function setAuthorizer(IAuthorizeMints authorizer_) external onlyOwner { mintAuthorizer = authorizer_; + emit AuthorizerUpdated(address(authorizer_)); } /// @notice https://docs.opensea.io/docs/contract-level-metadata diff --git a/src/Tokenizer.sol b/src/Tokenizer.sol index 717ac92a..aff91bc2 100644 --- a/src/Tokenizer.sol +++ b/src/Tokenizer.sol @@ -54,7 +54,11 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable { _disableInitializers(); } - function reinit(IPermissioner _permissioner) public onlyOwner reinitializer(2) { + /** + * @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; } diff --git a/src/helpers/Strings.sol b/src/helpers/Strings.sol new file mode 100644 index 00000000..d3166acc --- /dev/null +++ b/src/helpers/Strings.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +library Strings { + function stringToUint(string memory s) internal pure returns (uint256) { + bytes memory b = bytes(s); + uint256 result = 0; + for (uint256 i = 0; i < b.length; i++) { + if (uint8(b[i]) >= 48 && uint8(b[i]) <= 57) { + result = result * 10 + (uint256(uint8(b[i])) - 48); + } + } + return result; + } +} diff --git a/subgraph/abis/Molecules.json b/subgraph/abis/IPToken.json similarity index 100% rename from subgraph/abis/Molecules.json rename to subgraph/abis/IPToken.json diff --git a/subgraph/abis/SharedSalesDistributor.json b/subgraph/abis/SharedSalesDistributor.json index 3563f0e3..fd02cb89 100644 --- a/subgraph/abis/SharedSalesDistributor.json +++ b/subgraph/abis/SharedSalesDistributor.json @@ -159,7 +159,7 @@ { "inputs": [ { - "internalType": "contract Molecules", + "internalType": "contract IPToken", "name": "tokenContract", "type": "address" }, @@ -187,7 +187,7 @@ { "inputs": [ { - "internalType": "contract Molecules", + "internalType": "contract IPToken", "name": "tokenContract", "type": "address" }, @@ -210,7 +210,7 @@ { "inputs": [ { - "internalType": "contract Molecules", + "internalType": "contract IPToken", "name": "tokenContract", "type": "address" }, @@ -228,7 +228,7 @@ { "inputs": [ { - "internalType": "contract Molecules", + "internalType": "contract IPToken", "name": "tokenContract", "type": "address" }, diff --git a/subgraph/abis/TermsAcceptedPermissioner.json b/subgraph/abis/TermsAcceptedPermissioner.json index 9c6942c4..9c904cb6 100644 --- a/subgraph/abis/TermsAcceptedPermissioner.json +++ b/subgraph/abis/TermsAcceptedPermissioner.json @@ -32,7 +32,7 @@ { "inputs": [ { - "internalType": "contract Molecules", + "internalType": "contract IPToken", "name": "tokenContract", "type": "address" }, @@ -55,7 +55,7 @@ { "inputs": [ { - "internalType": "contract Molecules", + "internalType": "contract IPToken", "name": "tokenContract", "type": "address" }, @@ -120,7 +120,7 @@ { "inputs": [ { - "internalType": "contract Molecules", + "internalType": "contract IPToken", "name": "tokenContract", "type": "address" } diff --git a/subgraph/abis/Synthesizer.json b/subgraph/abis/Tokenizer.json similarity index 77% rename from subgraph/abis/Synthesizer.json rename to subgraph/abis/Tokenizer.json index 660e203c..a2193313 100644 --- a/subgraph/abis/Synthesizer.json +++ b/subgraph/abis/Tokenizer.json @@ -6,7 +6,7 @@ }, { "inputs": [], - "name": "AlreadySynthesized", + "name": "AlreadyTokenized", "type": "error" }, { @@ -59,6 +59,25 @@ "name": "Initialized", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -111,26 +130,7 @@ "type": "string" } ], - "name": "MoleculesCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", + "name": "TokensCreated", "type": "event" }, { @@ -190,6 +190,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IPermissioner", + "name": "_permissioner", + "type": "address" + } + ], + "name": "reinit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "renounceOwnership", @@ -197,6 +210,25 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "synthesized", + "outputs": [ + { + "internalType": "contract IPToken", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -206,7 +238,7 @@ }, { "internalType": "uint256", - "name": "moleculesAmount", + "name": "tokenAmount", "type": "uint256" }, { @@ -225,36 +257,17 @@ "type": "bytes" } ], - "name": "synthesizeIpnft", + "name": "tokenizeIpnft", "outputs": [ { - "internalType": "contract Molecules", - "name": "molecules", + "internalType": "contract IPToken", + "name": "token", "type": "address" } ], "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "synthesized", - "outputs": [ - { - "internalType": "contract Molecules", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -298,5 +311,60 @@ "outputs": [], "stateMutability": "payable", "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "moleculesId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "ipnftId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenContract", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "emitter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "agreementCid", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "symbol", + "type": "string" + } + ], + "name": "MoleculesCreated", + "type": "event" } ] diff --git a/subgraph/config/goerli.js b/subgraph/config/goerli.js index 55ac7bff..f09fedbd 100644 --- a/subgraph/config/goerli.js +++ b/subgraph/config/goerli.js @@ -14,7 +14,7 @@ module.exports = { address: '0x67D8ed102E2168A46FA342e39A5f7D16c103Bd0d', startBlock: 9099302 }, - synthesizer: { + tokenizer: { address: '0xb12494eeA6B992d0A1Db3C5423BE7a2d2337F58c', startBlock: 9142681 }, @@ -23,7 +23,7 @@ module.exports = { startBlock: 9168705 }, termsAcceptedPermissioner: { - address: '0x0045723801561079d94f0Bb1B65f322078E52635', - startBlock: 9148511 + address: '0xd735d9504cce32F2cd665b779D699B4157686fcd', + startBlock: 9417050 } } diff --git a/subgraph/config/local.js b/subgraph/config/local.js index 4bec6003..227a3e4f 100644 --- a/subgraph/config/local.js +++ b/subgraph/config/local.js @@ -1,7 +1,7 @@ const path = require('node:path') require('dotenv').config({ debug: true, - path: path.resolve(process.cwd(), '../.env.example') + path: path.resolve(process.cwd(), '../.env') }) module.exports = { @@ -14,8 +14,8 @@ module.exports = { address: process.env.SOS_ADDRESS, startBlock: 0 }, - synthesizer: { - address: process.env.SYNTHESIZER_ADDRESS, + tokenizer: { + address: process.env.TOKENIZER_ADDRESS, startBlock: 0 }, stakedLockingCrowdSale: { diff --git a/subgraph/config/mainnet.js b/subgraph/config/mainnet.js index 3fd4fcd0..3c238888 100644 --- a/subgraph/config/mainnet.js +++ b/subgraph/config/mainnet.js @@ -8,7 +8,7 @@ module.exports = { address: '0xc09b8577c762b5e97a7d640f242e1d9bfaa7eb9d', startBlock: 17441953 }, - synthesizer: { + tokenizer: { address: '0x58EB89C69CB389DBef0c130C6296ee271b82f436', startBlock: 17464363 }, diff --git a/subgraph/makeAbis.sh b/subgraph/makeAbis.sh index 96faff21..fc6e43b0 100755 --- a/subgraph/makeAbis.sh +++ b/subgraph/makeAbis.sh @@ -4,9 +4,70 @@ cat ../out/IERC20Metadata.sol/IERC20Metadata.json | jq .abi > abis/IERC20Metadat cat ../out/IPNFT.sol/IPNFT.json | jq .abi > ./abis/IPNFT.json cat ../out/Mintpass.sol/Mintpass.json | jq .abi > ./abis/Mintpass.json cat ../out/SchmackoSwap.sol/SchmackoSwap.json | jq .abi > ./abis/SchmackoSwap.json -cat ../out/Synthesizer.sol/Synthesizer.json | jq .abi > ./abis/Synthesizer.json -cat ../out/Molecules.sol/Molecules.json | jq .abi > ./abis/Molecules.json +cat ../out/IPToken.sol/IPToken.json | jq .abi > ./abis/IPToken.json cat ../out/TimelockedToken.sol/TimelockedToken.json | jq .abi > ./abis/TimelockedToken.json cat ../out/SalesShareDistributor.sol/SalesShareDistributor.json | jq .abi > ./abis/SharedSalesDistributor.json cat ../out/Permissioner.sol/TermsAcceptedPermissioner.json | jq .abi > ./abis/TermsAcceptedPermissioner.json cat ../out/StakedLockingCrowdSale.sol/StakedLockingCrowdSale.json | jq .abi > ./abis/StakedLockingCrowdSale.json + +cat ../out/Tokenizer.sol/Tokenizer.json | jq .abi > ./abis/_Tokenizer.json + +# add the old Synthesizer's `MoleculesCreated` event to the Tokenizer abi so the subgraph can index them +jq '. += [{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "moleculesId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "ipnftId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenContract", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "emitter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "agreementCid", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "symbol", + "type": "string" + } + ], + "name": "MoleculesCreated", + "type": "event" + }]' ./abis/_Tokenizer.json > ./abis/Tokenizer.json + + rm ./abis/_Tokenizer.json + diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 2ffd6961..b5e39ef8 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -6,36 +6,36 @@ type Ipnft @entity { symbol: String listings: [Listing!] @derivedFrom(field: "ipnft") readers: [CanRead!] @derivedFrom(field: "ipnft") - reactedIpnfts: [ReactedIpnft!] @derivedFrom(field: "ipnft") + ipts: [IPT!] @derivedFrom(field: "ipnft") } -type ReactedIpnft @entity { - id: ID! # Molecules tokenaddress +type IPT @entity { + id: ID! # ipt address name: String! symbol: String! decimals: BigInt! agreementCid: String! #IPFS CID string of FAM Agreement originalOwner: Bytes! # address of the issuer - # these will be updated by the underlying Molecules subgraph template - totalIssued: BigInt! #the highest amount of molecules issued, this can be raised by the owner - circulatingSupply: BigInt! #the amount of unburnt molecules + # these will be updated by the underlying IPT subgraph template + totalIssued: BigInt! #the highest amount of IPTs issued, this can be raised by the owner + circulatingSupply: BigInt! #the amount of unburnt IPTs # paymentToken: Bytes #address ERC20 Token # paidPrice: BigInt #the price paid for the original ipnft # claimedShares: BigInt! # the amount of shares that have already been claimed. This needs to have a fulfilledListingId to be set ipnft: Ipnft lockedToken: TimelockedToken #the locked token that bidders partially receive when claiming - molecules: [Molecule!] @derivedFrom(field: "reactedIpnft") - crowdsales: [CrowdSale!] @derivedFrom(field: "reactedIpnft") + iptBalances: [IPTBalance!] @derivedFrom(field: "ipt") + crowdsales: [CrowdSale!] @derivedFrom(field: "ipt") createdAt: BigInt! } -type Molecule @entity { +type IPTBalance @entity { id: ID! # erc20address + owner address - reactedIpnft: ReactedIpnft! + ipt: IPT! balance: BigInt! # Token balance of the owner owner: Bytes! #Address of the token owner agreementSignature: Bytes #the agreement signature, to be reused for other operations - schedules: [LockedSchedule!] @derivedFrom(field: "molecule") + schedules: [LockedSchedule!] @derivedFrom(field: "iptBalance") } type ERC20Token @entity { @@ -51,7 +51,7 @@ type TimelockedToken @entity { symbol: String! name: String underlyingToken: ERC20Token - reactedIpnft: ReactedIpnft + ipt: IPT schedules: [LockedSchedule!] @derivedFrom(field: "tokenContract") } @@ -62,7 +62,7 @@ type LockedSchedule @entity { amount: BigInt! expiresAt: BigInt! claimedAt: BigInt - molecule: Molecule! + iptBalance: IPTBalance! } type Listing @entity { @@ -109,7 +109,7 @@ enum SaleState { type CrowdSale @entity { id: ID! - reactedIpnft: ReactedIpnft! + ipt: IPT! issuer: Bytes! #address of the creator beneficiary: Bytes! #address of the receiver of fundingGoal (usually issuer) closingTime: BigInt! #the time when the auction ends @@ -125,6 +125,7 @@ type CrowdSale @entity { vestedStakingToken: ERC20Token #the vested staking token that bidders partially receive after claiming stakingDuration: BigInt! # The duration for how long staked tokens are locked ("cliff" in vesting terms) wadFixedStakedPerBidPrice: BigInt # the fixed price of stake / bid token + permissioner: Bytes! #a crowdsale can be configured with an individual permissioner claimedAt: BigInt # the date when the auctioneer has claimed their return (depending on failed / settled sales) contributions: [Contribution!] @derivedFrom(field: "crowdSale") } diff --git a/subgraph/src/iptMapping.ts b/subgraph/src/iptMapping.ts new file mode 100644 index 00000000..eb35258f --- /dev/null +++ b/subgraph/src/iptMapping.ts @@ -0,0 +1,70 @@ +import { Address, BigInt, log } from '@graphprotocol/graph-ts' +import { IPT, IPTBalance } from '../generated/schema' +import { Transfer as TransferEvent } from '../generated/templates/IPToken/IPToken' + +function createOrUpdateBalances( + owner: Address, + address: string, + value: BigInt +): void { + let balanceId = address + '-' + owner.toHexString() + let balance = IPTBalance.load(balanceId) + if (!balance) { + balance = new IPTBalance(balanceId) + balance.ipt = address + balance.balance = value + balance.owner = owner + balance.agreementSignature = null + } else { + balance.balance = balance.balance.plus(value) + } + balance.save() +} + +export function handleTransfer(event: TransferEvent): void { + let from = event.params.from + let to = event.params.to + let value = event.params.value + + let ipt = IPT.load(event.address.toHexString()) + if (!ipt) { + log.error('[IPT] Ipnft not found for id: {}', [event.address.toHexString()]) + return + } + + //mint + if (from == Address.zero()) { + createOrUpdateBalances(to, event.address.toHexString(), value) + ipt.totalIssued = ipt.totalIssued.plus(value) + ipt.circulatingSupply = ipt.circulatingSupply.plus(value) + ipt.save() + + return + } + + //burn + if (to == Address.zero()) { + createOrUpdateBalances(from, event.address.toHexString(), value.neg()) + ipt.circulatingSupply = ipt.circulatingSupply.minus(value) + ipt.save() + return + } + + //transfer + createOrUpdateBalances(from, event.address.toHexString(), value.neg()) + createOrUpdateBalances(to, event.address.toHexString(), value) +} + +// export function handleSharesClaimed(event: SharesClaimedEvent): void { +// let reactedIpnft = ReactedIpnft.load(event.params.moleculesId.toString()); +// if (!reactedIpnft) { +// log.error('ReactedIpnft ipnft not found for id: {}', [ +// event.params.moleculesId.toString() +// ]); +// return; +// } +// reactedIpnft.claimedShares = reactedIpnft.claimedShares.plus( +// event.params.amount +// ); +// reactedIpnft.save(); +// } diff --git a/subgraph/src/moleculesMapping.ts b/subgraph/src/moleculesMapping.ts deleted file mode 100644 index bf45f384..00000000 --- a/subgraph/src/moleculesMapping.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Address, BigInt, log } from '@graphprotocol/graph-ts' -import { - Transfer as TransferEvent, - Capped as CappedEvent - //SharesClaimed as SharesClaimedEvent -} from '../generated/templates/Molecules/Molecules' -import { ReactedIpnft, Molecule, Ipnft } from '../generated/schema' - -function createOrUpdateMolecules( - owner: Address, - address: string, - value: BigInt -): void { - let moleculesId = address + '-' + owner.toHexString() - let molecule = Molecule.load(moleculesId) - if (!molecule) { - molecule = new Molecule(moleculesId) - molecule.reactedIpnft = address - molecule.balance = value - molecule.owner = owner - molecule.agreementSignature = null - } else { - molecule.balance = molecule.balance.plus(value) - } - molecule.save() -} - -export function handleTransfer(event: TransferEvent): void { - let from = event.params.from - let to = event.params.to - let value = event.params.value - - let reactedIpnft = ReactedIpnft.load(event.address.toHexString()) - if (!reactedIpnft) { - log.error('ReactedIpnft Ipnft not found for id: {}', [ - event.address.toHexString() - ]) - return - } - - //mint - if (from == Address.zero()) { - createOrUpdateMolecules(to, event.address.toHexString(), value) - reactedIpnft.totalIssued = reactedIpnft.totalIssued.plus(value) - reactedIpnft.circulatingSupply = reactedIpnft.circulatingSupply.plus(value) - reactedIpnft.save() - - return - } - - //burn - if (to == Address.zero()) { - createOrUpdateMolecules(from, event.address.toHexString(), value.neg()) - reactedIpnft.circulatingSupply = reactedIpnft.circulatingSupply.minus(value) - reactedIpnft.save() - return - } - - //transfer - createOrUpdateMolecules(from, event.address.toHexString(), value.neg()) - createOrUpdateMolecules(to, event.address.toHexString(), value) -} - -// export function handleSharesClaimed(event: SharesClaimedEvent): void { -// let reactedIpnft = ReactedIpnft.load(event.params.moleculesId.toString()); -// if (!reactedIpnft) { -// log.error('ReactedIpnft ipnft not found for id: {}', [ -// event.params.moleculesId.toString() -// ]); -// return; -// } -// reactedIpnft.claimedShares = reactedIpnft.claimedShares.plus( -// event.params.amount -// ); -// reactedIpnft.save(); -// } diff --git a/subgraph/src/stakedLockingCrowdSaleMapping.ts b/subgraph/src/stakedLockingCrowdSaleMapping.ts index 0f11f209..35dbbdf5 100644 --- a/subgraph/src/stakedLockingCrowdSaleMapping.ts +++ b/subgraph/src/stakedLockingCrowdSaleMapping.ts @@ -17,7 +17,7 @@ import { Contribution, CrowdSale, ERC20Token, - ReactedIpnft, + IPT, TimelockedToken } from '../generated/schema' @@ -52,11 +52,11 @@ function makeTimelockedToken( token.name = _contract.name() token.underlyingToken = underlyingToken.id - let reactedIpnft = ReactedIpnft.load(underlyingToken.id.toHexString()) - if (reactedIpnft) { - token.reactedIpnft = reactedIpnft.id - reactedIpnft.lockedToken = token.id - reactedIpnft.save() + let ipt = IPT.load(underlyingToken.id.toHexString()) + if (ipt) { + token.ipt = ipt.id + ipt.lockedToken = token.id + ipt.save() } token.save() } @@ -67,33 +67,29 @@ function makeTimelockedToken( export function handleStarted(event: StartedEvent): void { let crowdSale = new CrowdSale(event.params.saleId.toString()) - let reactedIpnft = ReactedIpnft.load( - event.params.sale.auctionToken.toHexString() - ) - if (!reactedIpnft) { - log.error('ReactedIpnft Ipnft not found for id: {}', [ + let ipt = IPT.load(event.params.sale.auctionToken.toHexString()) + if (!ipt) { + log.error('[Crowdsale] Ipt not found for id: {}', [ event.params.sale.auctionToken.toHexString() ]) return } - if (!reactedIpnft.lockedToken) { - reactedIpnft.lockedToken = event.params.lockingToken - reactedIpnft.save() + if (!ipt.lockedToken) { + ipt.lockedToken = event.params.lockingToken + ipt.save() } else { - let _reactedIpnftToken = changetype( - reactedIpnft.lockedToken - ).toHexString() + let _ipt = changetype(ipt.lockedToken).toHexString() let _newToken = event.params.lockingToken.toHexString() - if (_reactedIpnftToken != _newToken) { - log.error( - 'the locking token per reacted Ipnft should be unique {} != {}', - [_reactedIpnftToken, _newToken] - ) + if (_ipt != _newToken) { + log.error('the locking token per IPT should be unique {} != {}', [ + _ipt, + _newToken + ]) } } - crowdSale.reactedIpnft = reactedIpnft.id + crowdSale.ipt = ipt.id crowdSale.issuer = event.params.issuer crowdSale.beneficiary = event.params.sale.beneficiary crowdSale.closingTime = event.params.sale.closingTime @@ -121,6 +117,7 @@ export function handleStarted(event: StartedEvent): void { crowdSale.wadFixedStakedPerBidPrice = event.params.staking.wadFixedStakedPerBidPrice + crowdSale.permissioner = event.params.sale.permissioner crowdSale.save() log.info('[handleStarted] crowdsale {}', [crowdSale.id]) } @@ -151,7 +148,7 @@ export function handleLockingContractCreated( event: LockingContractCreatedEvent ): void { let context = new DataSourceContext() - context.setBytes('reactedIpnft', event.params.underlyingToken) + context.setBytes('ipt', event.params.underlyingToken) context.setBytes('lockingContract', event.params.lockingContract) TimelockedTokenTemplate.createWithContext( event.params.lockingContract, @@ -272,10 +269,11 @@ export function handleClaimed(event: ClaimedEvent): void { contribution.refundedTokens = event.params.refunded contribution.save() } + export function handleClaimedStakes(event: ClaimedStakesEvent): void { let contributionId = event.params.saleId.toString() + '-' + event.params.claimer.toHex() - // Load Contribution + // Load Contribution let contribution = Contribution.load(contributionId) if (contribution === null) { log.error( diff --git a/subgraph/src/termsAcceptedPermissionerMapping.ts b/subgraph/src/termsAcceptedPermissionerMapping.ts index 2b8a4653..bb059aee 100644 --- a/subgraph/src/termsAcceptedPermissionerMapping.ts +++ b/subgraph/src/termsAcceptedPermissionerMapping.ts @@ -1,25 +1,25 @@ import { BigInt, log } from '@graphprotocol/graph-ts' import { TermsAccepted as TermsAcceptedEvent } from '../generated/TermsAcceptedPermissioner/TermsAcceptedPermissioner' -import { Molecule, ReactedIpnft } from '../generated/schema' +import { IPTBalance, IPT } from '../generated/schema' export function handleTermsAccepted(event: TermsAcceptedEvent): void { - let moleculesId = + let balanceId = event.params.tokenContract.toHexString() + '-' + event.params.signer.toHexString() - let molecule = Molecule.load(moleculesId) + let iptBalance = IPTBalance.load(balanceId) - if (!molecule) { - let reacted = ReactedIpnft.load(event.params.tokenContract.toHexString()) + if (!iptBalance) { + let reacted = IPT.load(event.params.tokenContract.toHexString()) if (!reacted) { - log.warning('molecules {} not found for signature', [moleculesId]) + log.warning('molecules {} not found for signature', [balanceId]) } - molecule = new Molecule(moleculesId) - molecule.owner = event.params.signer - molecule.reactedIpnft = event.params.tokenContract.toHexString() - molecule.balance = BigInt.fromI32(0) + iptBalance = new IPTBalance(balanceId) + iptBalance.owner = event.params.signer + iptBalance.ipt = event.params.tokenContract.toHexString() + iptBalance.balance = BigInt.fromI32(0) } - molecule.agreementSignature = event.params.signature - molecule.save() + iptBalance.agreementSignature = event.params.signature + iptBalance.save() } diff --git a/subgraph/src/timelockedTokenMapping.ts b/subgraph/src/timelockedTokenMapping.ts index 8025a8de..4eaa33e3 100644 --- a/subgraph/src/timelockedTokenMapping.ts +++ b/subgraph/src/timelockedTokenMapping.ts @@ -1,6 +1,6 @@ import { dataSource, log, BigInt } from '@graphprotocol/graph-ts' -import { LockedSchedule, Molecule } from '../generated/schema' +import { LockedSchedule, IPTBalance } from '../generated/schema' import { ScheduleCreated, ScheduleReleased @@ -10,20 +10,20 @@ export function handleScheduled(event: ScheduleCreated): void { let context = dataSource.context() let schedule = new LockedSchedule(event.params.scheduleId) - let reactedIpnft = context.getBytes('reactedIpnft').toHexString() - let moleculesId = reactedIpnft + '-' + event.params.beneficiary.toHexString() - - let molecule = Molecule.load(moleculesId) - if (!molecule) { - molecule = new Molecule(moleculesId) - molecule.reactedIpnft = reactedIpnft - molecule.balance = BigInt.fromI32(0) - molecule.owner = event.params.beneficiary - molecule.agreementSignature = null - molecule.save() + let ipt = context.getBytes('ipt').toHexString() + let balanceId = ipt + '-' + event.params.beneficiary.toHexString() + + let iptBalance = IPTBalance.load(balanceId) + if (!iptBalance) { + iptBalance = new IPTBalance(balanceId) + iptBalance.ipt = ipt + iptBalance.balance = BigInt.fromI32(0) + iptBalance.owner = event.params.beneficiary + iptBalance.agreementSignature = null + iptBalance.save() } - schedule.molecule = moleculesId + schedule.iptBalance = balanceId schedule.tokenContract = context.getBytes('lockingContract') schedule.beneficiary = event.params.beneficiary schedule.amount = event.params.amount diff --git a/subgraph/src/synthesizerMapping.ts b/subgraph/src/tokenizerMapping.ts similarity index 78% rename from subgraph/src/synthesizerMapping.ts rename to subgraph/src/tokenizerMapping.ts index 5553b0ee..6227f714 100644 --- a/subgraph/src/synthesizerMapping.ts +++ b/subgraph/src/tokenizerMapping.ts @@ -1,12 +1,15 @@ import { BigInt } from '@graphprotocol/graph-ts' -import { MoleculesCreated as MoleculesCreatedEvent } from '../generated/Synthesizer/Synthesizer' +import { + MoleculesCreated as MoleculesCreatedEvent, + TokensCreated as TokensCreatedEvent +} from '../generated/Tokenizer/Tokenizer' -import { Molecules } from '../generated/templates' +import { IPToken } from '../generated/templates' -import { ReactedIpnft } from '../generated/schema' +import { IPT } from '../generated/schema' -export function handleMoleculesCreated(event: MoleculesCreatedEvent): void { - let reacted = new ReactedIpnft(event.params.tokenContract.toHexString()) +export function handleIPTsCreated(event: TokensCreatedEvent): void { + let reacted = new IPT(event.params.tokenContract.toHexString()) reacted.createdAt = event.block.timestamp reacted.ipnft = event.params.ipnftId.toString() @@ -19,7 +22,7 @@ export function handleMoleculesCreated(event: MoleculesCreatedEvent): void { //these will be updated by the underlying Molecules subgraph template reacted.totalIssued = BigInt.fromU32(0) reacted.circulatingSupply = BigInt.fromU32(0) - Molecules.create(event.params.tokenContract) + IPToken.create(event.params.tokenContract) reacted.save() } diff --git a/subgraph/subgraph.template.yaml b/subgraph/subgraph.template.yaml index 7d83b424..c8b16811 100644 --- a/subgraph/subgraph.template.yaml +++ b/subgraph/subgraph.template.yaml @@ -56,30 +56,33 @@ dataSources: file: ./src/swapMapping.ts - kind: ethereum/contract - name: Synthesizer + name: Tokenizer network: {{network}} source: - address: '{{synthesizer.address}}' - abi: Synthesizer - startBlock: {{synthesizer.startBlock}} + address: '{{tokenizer.address}}' + abi: Tokenizer + startBlock: {{tokenizer.startBlock}} mapping: kind: ethereum/events apiVersion: 0.0.6 language: wasm/assemblyscript entities: - - ReactedIpnft - - Molecule + - IPT + - IPTBalance abis: - - name: Synthesizer - file: ./abis/Synthesizer.json + - name: Tokenizer + file: ./abis/Tokenizer.json eventHandlers: + - event: TokensCreated(indexed uint256,indexed uint256,indexed address,address,uint256,string,string,string) + handler: handleIPTsCreated + # the legacy event - event: MoleculesCreated(indexed uint256,indexed uint256,indexed address,address,uint256,string,string,string) - handler: handleMoleculesCreated + handler: handleIPTsCreated # - event: SalesActivated(uint256,address,uint256) # handler: handleSalesActivated # - event: TermsAccepted(indexed uint256,indexed address,bytes) # handler: handleTermsAccepted - file: ./src/synthesizerMapping.ts + file: ./src/tokenizerMapping.ts - kind: ethereum/contract name: StakedLockingCrowdSale network: {{network}} @@ -135,7 +138,7 @@ dataSources: apiVersion: 0.0.6 language: wasm/assemblyscript entities: - - Molecule + - IPTBalance abis: - name: TermsAcceptedPermissioner file: ./abis/TermsAcceptedPermissioner.json @@ -143,22 +146,23 @@ dataSources: - event: TermsAccepted(indexed address,indexed address,bytes) handler: handleTermsAccepted file: ./src/termsAcceptedPermissionerMapping.ts + templates: - - name: Molecules + - name: IPToken kind: ethereum/contract network: {{network}} source: - abi: Molecules + abi: IPToken mapping: kind: ethereum/events apiVersion: 0.0.6 language: wasm/assemblyscript - file: ./src/moleculesMapping.ts + file: ./src/iptMapping.ts entities: - - Molecule + - IPTBalance abis: - - name: Molecules - file: ./abis/Molecules.json + - name: IPToken + file: ./abis/IPToken.json eventHandlers: - event: Transfer(indexed address,indexed address,uint256) handler: handleTransfer diff --git a/test/IPNFTUpgrades.t.sol b/test/IPNFTUpgrades.t.sol index 452e1eb7..edceeaa4 100644 --- a/test/IPNFTUpgrades.t.sol +++ b/test/IPNFTUpgrades.t.sol @@ -15,6 +15,7 @@ import { IPNFTV25 } from "../src/helpers/test-upgrades/IPNFTV25.sol"; contract IPNFTUpgrades is IPNFTMintHelper { event Reserved(address indexed reserver, uint256 indexed reservationId); + event AuthorizerUpdated(address authorizer); IPNFTV23 internal ipnftV23; IPNFT internal ipnft; @@ -46,9 +47,11 @@ contract IPNFTUpgrades is IPNFTMintHelper { ipnftV23.upgradeTo(address(implementationV24)); ipnft = IPNFT(address(ipnftV23)); - ipnft.setAuthorizer(new SignedMintAuthorizer(deployer)); + SignedMintAuthorizer authorizer = new SignedMintAuthorizer(deployer); - //ipnft.reinit(); + vm.expectEmit(true, true, false, false); + emit AuthorizerUpdated(address(authorizer)); + ipnft.setAuthorizer(authorizer); } function testUpgradeContract() public { diff --git a/test/SynthesizerUpgrade.t.sol b/test/SynthesizerUpgrade.t.sol index f00e44a4..65a6d30b 100644 --- a/test/SynthesizerUpgrade.t.sol +++ b/test/SynthesizerUpgrade.t.sol @@ -125,6 +125,10 @@ contract SynthesizerUpgradeTest is Test { TermsAcceptedPermissioner termsPermissioner = new TermsAcceptedPermissioner(); tokenizer.reinit(termsPermissioner); + + vm.expectRevert("Initializable: contract is already initialized"); + tokenizer.reinit(termsPermissioner); + vm.stopPrank(); assertEq(tokenContractOld.balanceOf(originalOwner), 100_000); From 8012a10b80928ebb68641a920b0776491da46fbf Mon Sep 17 00:00:00 2001 From: stadolf Date: Fri, 28 Jul 2023 11:19:42 +0200 Subject: [PATCH 5/6] new deployment addresses sg config uses env.example deterministic addresses again fixes old natspec comments (too late but right :) ) Signed-off-by: stadolf --- README.md | 19 ++++++++++++++----- script/dev/CrowdSale.s.sol | 4 ++-- src/Permissioner.sol | 4 ++-- src/SignedMintAuthorizer.sol | 1 - subgraph/config/local.js | 2 +- subgraph/config/mainnet.js | 4 ++-- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 027176e5..74252075 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,25 @@ IP-NFTs allow their users to tokenize intellectual property. This repo contains | Contract | Address | Actions | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | IP-NFT | [0xcaD88677CA87a7815728C72D74B4ff4982d54Fc1](https://etherscan.io/address/0xcaD88677CA87a7815728C72D74B4ff4982d54Fc1#code>) | View contract | +| SignedMintAuthorizer | [0xBc5FbB45A2bbB64d9B2EeBFa327284a35d5C5865](https://etherscan.io/address/0xBc5FbB45A2bbB64d9B2EeBFa327284a35d5C5865#code) | View contract | | SchmackoSwap | [0xc09b8577c762b5e97a7d640f242e1d9bfaa7eb9d](https://etherscan.io/address/0xc09b8577c762b5e97a7d640f242e1d9bfaa7eb9d#code) | View contract | -| Mintpass | [0x0ecff38f41ecd1e978f1443ed96c0c22497d73cb](https://etherscan.io/address/0x0ecff38f41ecd1e978f1443ed96c0c22497d73cb#code) | View contract | -| Synthesizer | [0x58EB89C69CB389DBef0c130C6296ee271b82f436](https://etherscan.io/address/0x58EB89C69CB389DBef0c130C6296ee271b82f436#code) | View contract | -| Permissioner | [0xC3191dEFE827D504885f47cfB3fE0919EBd35705](https://etherscan.io/address/0xC3191dEFE827D504885f47cfB3fE0919EBd35705#code) | View contract | +| Tokenizer | [0x58EB89C69CB389DBef0c130C6296ee271b82f436](https://etherscan.io/address/0x58EB89C69CB389DBef0c130C6296ee271b82f436#code) | View contract | +| Permissioner | [0xC837E02982992B701A1B5e4E21fA01cEB0a628fA](https://etherscan.io/address/0xC837E02982992B701A1B5e4E21fA01cEB0a628fA#code) | View contract | | StakedLockingCrowdSale | [0x35Bce29F52f51f547998717CD598068Afa2B29B7](https://etherscan.io/address/0xC3191dEFE827D504885f47cfB3fE0919EBd35705#code) | View contract | +| Mintpass | [0x0ecff38f41ecd1e978f1443ed96c0c22497d73cb](https://etherscan.io/address/0x0ecff38f41ecd1e978f1443ed96c0c22497d73cb#code) | View contract | - Subgraph: -synthImpl implementation 1: 0xb050A85933FF0807f05d289b7f6457c5eFbC348f -ipnft implementation 2.3: 0x0443DfAC8E510cFBDFdb9247E77400E9728aE45D +tokenizer implementation 2: 0x9C70FA8c87D7e94Fd63eeCCcA657D5c4224a36f3 +ipnft implementation 2.4: 0x6B179Dffac5E190c670176606f552cB792847f80 + +Defender Relayer that signs off minting requests from our side: +0x3D30452c48F2448764d5819a9A2b684Ae2CC5AcF + +SIGNED_MINT_AUTHORIZER=0xBc5FbB45A2bbB64d9B2EeBFa327284a35d5C5865 +TERMS_ACCEPTED_PERMISSIONER_ADDRESS=0xC837E02982992B701A1B5e4E21fA01cEB0a628fA +new ipnft impl: 0x6B179Dffac5E190c670176606f552cB792847f80 +new tokenizer impl: 0x9C70FA8c87D7e94Fd63eeCCcA657D5c4224a36f3 --- diff --git a/script/dev/CrowdSale.s.sol b/script/dev/CrowdSale.s.sol index 0f367f13..15441728 100644 --- a/script/dev/CrowdSale.s.sol +++ b/script/dev/CrowdSale.s.sol @@ -110,7 +110,7 @@ contract FixtureCrowdSale is CommonScript { auctionToken.approve(address(stakedLockingCrowdSale), 400 ether); uint256 saleId = stakedLockingCrowdSale.startSale(_sale, daoToken, vestedDaoToken, 1e18, 7 days); - TimelockedToken lockedMolToken = stakedLockingCrowdSale.lockingContracts(address(auctionToken)); + TimelockedToken lockedIpt = stakedLockingCrowdSale.lockingContracts(address(auctionToken)); vm.stopBroadcast(); string memory terms = permissioner.specificTermsV1(auctionToken); @@ -119,7 +119,7 @@ contract FixtureCrowdSale is CommonScript { placeBid(alice, 600 ether, saleId, abi.encodePacked(r, s, v)); (v, r, s) = vm.sign(charliePk, ECDSA.toEthSignedMessageHash(abi.encodePacked(terms))); placeBid(charlie, 200 ether, saleId, abi.encodePacked(r, s, v)); - console.log("LOCKED_IPTS_ADDRESS=%s", address(lockedMolToken)); + console.log("LOCKED_IPTS_ADDRESS=%s", address(lockedIpt)); console.log("SALE_ID=%s", saleId); vm.writeFile("SALEID.txt", Strings.toString(saleId)); } diff --git a/src/Permissioner.sol b/src/Permissioner.sol index b6ffa250..625bbd6e 100644 --- a/src/Permissioner.sol +++ b/src/Permissioner.sol @@ -53,7 +53,7 @@ contract TermsAcceptedPermissioner is IPermissioner { /** * @notice checks whether `signer`'s `signature` of `specificTermsV1` on `tokenContract.metadata.ipnftId` is valid - * @param tokenContract Molecules + * @param tokenContract IPToken */ function isValidSignature(IPToken tokenContract, address signer, bytes calldata signature) public view returns (bool) { bytes32 termsHash = ECDSA.toEthSignedMessageHash(bytes(specificTermsV1(tokenContract))); @@ -76,7 +76,7 @@ contract TermsAcceptedPermissioner is IPermissioner { /** * @notice this yields the message text that claimers must present as signed message to burn their molecules and claim shares - * @param tokenContract ISynthesizedToken + * @param tokenContract IPToken */ function specificTermsV1(IPToken tokenContract) public view returns (string memory) { return (specificTermsV1(tokenContract.metadata())); diff --git a/src/SignedMintAuthorizer.sol b/src/SignedMintAuthorizer.sol index af5db11f..84e51b0c 100644 --- a/src/SignedMintAuthorizer.sol +++ b/src/SignedMintAuthorizer.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.18; -import "forge-std/console.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { IAuthorizeMints, SignedMintAuthorization } from "./IAuthorizeMints.sol"; diff --git a/subgraph/config/local.js b/subgraph/config/local.js index 227a3e4f..550ba5dc 100644 --- a/subgraph/config/local.js +++ b/subgraph/config/local.js @@ -1,7 +1,7 @@ const path = require('node:path') require('dotenv').config({ debug: true, - path: path.resolve(process.cwd(), '../.env') + path: path.resolve(process.cwd(), '../.env.example') }) module.exports = { diff --git a/subgraph/config/mainnet.js b/subgraph/config/mainnet.js index 3c238888..3fd5557d 100644 --- a/subgraph/config/mainnet.js +++ b/subgraph/config/mainnet.js @@ -17,7 +17,7 @@ module.exports = { startBlock: 17481804 }, termsAcceptedPermissioner: { - address: '0xC3191dEFE827D504885f47cfB3fE0919EBd35705', - startBlock: 17441953 + address: '0xC837E02982992B701A1B5e4E21fA01cEB0a628fA', + startBlock: 17790450 } } From 5289ea38c0d76c2d0202187631d850c89036b3d0 Mon Sep 17 00:00:00 2001 From: stadolf Date: Fri, 28 Jul 2023 14:36:18 +0200 Subject: [PATCH 6/6] gas reports & coverage renames testing symbols updates sequence diagram Signed-off-by: stadolf --- .gas-report | 169 +++++++++--------- .gas-snapshot | 37 ++-- SEQUENCE.md | 48 ++--- audit/TESTS.md | 18 +- foundry.toml | 2 +- src/Permissioner.sol | 2 +- .../src/termsAcceptedPermissionerMapping.ts | 2 +- subgraph/src/tokenizerMapping.ts | 22 +-- test/CrowdSale.t.sol | 2 +- test/CrowdSaleFuzz.t.sol | 2 +- test/CrowdSaleLockedStakedTest.t.sol | 2 +- test/CrowdSaleLockedTest.t.sol | 2 +- test/CrowdSalePermissioned.t.sol | 2 +- test/CrowdSaleWithNonStandardERC20Test.t.sol | 2 +- test/IPNFTMintHelper.sol | 2 +- test/SchmackoSwap.t.sol | 2 +- test/Tokenizer.t.sol | 20 +-- 17 files changed, 158 insertions(+), 178 deletions(-) diff --git a/.gas-report b/.gas-report index 01164907..7b8ac204 100644 --- a/.gas-report +++ b/.gas-report @@ -1,82 +1,67 @@ | src/IPNFT.sol:IPNFT contract | | | | | | -|-------------------------------------------------|-----------------|--------|--------|--------|---------| +| ----------------------------------------------- | --------------- | ------ | ------ | ------ | ------- | | Deployment Cost | Deployment Size | | | | | -| 2558308 | 12947 | | | | | +| 2567323 | 12992 | | | | | | Function Name | min | avg | median | max | # calls | | balanceOf | 629 | 1295 | 629 | 2629 | 9 | | burn | 5806 | 5806 | 5806 | 5806 | 1 | | canRead | 816 | 1428 | 1082 | 3082 | 5 | | fallback | 46 | 46 | 46 | 46 | 1 | | grantReadAccess | 723 | 8053 | 745 | 22692 | 3 | -| initialize | 2791 | 115676 | 118499 | 118499 | 41 | +| initialize | 2791 | 115755 | 118511 | 118511 | 42 | | isApprovedForAll | 808 | 1933 | 2808 | 2808 | 16 | -| mintReservation | 1486 | 127198 | 128006 | 150605 | 39 | -| ownerOf | 624 | 1766 | 2624 | 2624 | 35 | +| mintReservation | 1486 | 127784 | 128006 | 150605 | 40 | +| ownerOf | 624 | 1790 | 2624 | 2624 | 36 | | pause | 25810 | 25810 | 25810 | 25810 | 1 | | paused | 2382 | 2382 | 2382 | 2382 | 1 | | proxiableUUID | 352 | 352 | 352 | 352 | 3 | | reservations | 568 | 568 | 568 | 568 | 3 | -| reserve | 512 | 27646 | 25329 | 36629 | 41 | +| reserve | 512 | 27591 | 25329 | 36629 | 42 | | safeTransferFrom(address,address,uint256) | 3858 | 28642 | 31941 | 37069 | 14 | | safeTransferFrom(address,address,uint256,bytes) | 21710 | 21710 | 21710 | 21710 | 1 | | setApprovalForAll | 6104 | 23797 | 24729 | 24729 | 20 | -| setAuthorizer | 3598 | 21098 | 22698 | 22698 | 44 | -| symbol | 1486 | 1486 | 1486 | 1486 | 4 | +| setAuthorizer | 4652 | 22187 | 23752 | 23752 | 45 | +| symbol | 1486 | 1486 | 1486 | 1486 | 3 | | tokenURI | 2122 | 2122 | 2122 | 2122 | 1 | | upgradeTo | 3584 | 3584 | 3584 | 3584 | 1 | | withdrawAll | 4604 | 8482 | 9422 | 11422 | 3 | - -| src/Mintpass.sol:Mintpass contract | | | | | | -|------------------------------------|-----------------|-------|--------|--------|---------| -| Deployment Cost | Deployment Size | | | | | -| 1821339 | 9226 | | | | | -| Function Name | min | avg | median | max | # calls | -| MODERATOR | 307 | 307 | 307 | 307 | 16 | -| authorizeMint | 8871 | 8871 | 8871 | 8871 | 2 | -| authorizeReservation | 676 | 676 | 676 | 676 | 2 | -| balanceOf | 679 | 879 | 679 | 2679 | 10 | -| batchMint | 30359 | 71481 | 72050 | 169756 | 15 | -| burn | 7978 | 25032 | 25032 | 42086 | 2 | -| grantRole | 27565 | 27698 | 27565 | 29565 | 15 | -| isRedeemable | 1064 | 3064 | 3064 | 5064 | 3 | -| ownerOf | 3042 | 19456 | 3042 | 113881 | 8 | -| redeem | 23250 | 23250 | 23250 | 23250 | 2 | -| revoke | 26772 | 29515 | 29515 | 32258 | 2 | -| revokeRole | 2845 | 2845 | 2845 | 2845 | 1 | -| tokenURI | 5846 | 19109 | 25664 | 25817 | 3 | -| totalSupply | 2633 | 2633 | 2633 | 2633 | 1 | -| transferFrom | 1440 | 1440 | 1440 | 1440 | 1 | - - -| src/Molecules.sol:Molecules contract | | | | | | -|--------------------------------------|-----------------|--------|--------|--------|---------| -| Deployment Cost | Deployment Size | | | | | -| 1579252 | 7920 | | | | | -| Function Name | min | avg | median | max | # calls | -| allowance | 2775 | 2775 | 2775 | 2775 | 1 | -| approve | 22546 | 24226 | 24646 | 24646 | 5 | -| balanceOf | 576 | 656 | 576 | 2576 | 25 | -| burnFrom | 4444 | 4444 | 4444 | 4444 | 3 | -| cap | 545 | 19327 | 21675 | 21675 | 9 | -| capped | 443 | 443 | 443 | 443 | 8 | -| decimals | 200 | 200 | 200 | 200 | 1 | -| hash | 645 | 645 | 645 | 645 | 15 | -| initialize | 161418 | 198409 | 205808 | 205808 | 18 | -| issue | 779 | 59746 | 71357 | 71357 | 18 | -| issuer | 432 | 432 | 432 | 432 | 15 | -| metadata | 1794 | 2581 | 2079 | 7794 | 29 | -| setAStateVar | 260 | 260 | 260 | 260 | 1 | -| symbol | 1284 | 1284 | 1284 | 1284 | 1 | -| totalIssued | 405 | 405 | 405 | 405 | 10 | -| totalSupply | 382 | 382 | 382 | 382 | 4 | -| transfer | 20034 | 24040 | 25042 | 25042 | 10 | -| transferFrom | 22233 | 24153 | 24153 | 26073 | 2 | -| uri | 65913 | 65913 | 65913 | 65913 | 1 | - +| src/IPToken.sol:IPToken contract | | | | | | +| -------------------------------- | --------------- | ------ | ------ | ------ | ------- | +| Deployment Cost | Deployment Size | | | | | +| 1579252 | 7920 | | | | | +| Function Name | min | avg | median | max | # calls | +| allowance | 2775 | 2775 | 2775 | 2775 | 1 | +| approve | 22546 | 24226 | 24646 | 24646 | 5 | +| balanceOf | 576 | 652 | 576 | 2576 | 26 | +| burnFrom | 4444 | 4444 | 4444 | 4444 | 3 | +| cap | 545 | 19327 | 21675 | 21675 | 9 | +| capped | 443 | 443 | 443 | 443 | 8 | +| decimals | 200 | 200 | 200 | 200 | 1 | +| hash | 645 | 645 | 645 | 645 | 15 | +| initialize | 161418 | 198409 | 205808 | 205808 | 18 | +| issue | 779 | 56787 | 71357 | 71357 | 19 | +| issuer | 432 | 432 | 432 | 432 | 15 | +| metadata | 1794 | 2565 | 2079 | 7794 | 30 | +| symbol | 1284 | 1284 | 1284 | 1284 | 1 | +| totalIssued | 405 | 405 | 405 | 405 | 10 | +| totalSupply | 382 | 382 | 382 | 382 | 4 | +| transfer | 20034 | 24040 | 25042 | 25042 | 10 | +| transferFrom | 22233 | 24153 | 24153 | 26073 | 2 | +| uri | 65913 | 65913 | 65913 | 65913 | 2 | + +| src/Permissioner.sol:TermsAcceptedPermissioner contract | | | | | | +| ------------------------------------------------------- | --------------- | ----- | ------ | ----- | ------- | +| Deployment Cost | Deployment Size | | | | | +| 603038 | 3044 | | | | | +| Function Name | min | avg | median | max | # calls | +| accept | 8954 | 14670 | 14345 | 27420 | 11 | +| isValidSignature | 11266 | 17584 | 17584 | 23902 | 2 | +| specificTermsV1((uint256,address,string))(string) | 3859 | 3859 | 3859 | 3859 | 2 | +| specificTermsV1(address)(string) | 6831 | 10290 | 9482 | 14557 | 6 | | src/SchmackoSwap.sol:SchmackoSwap contract | | | | | | -|--------------------------------------------------------|-----------------|--------|--------|--------|---------| +| ------------------------------------------------------ | --------------- | ------ | ------ | ------ | ------- | | Deployment Cost | Deployment Size | | | | | | 977980 | 4705 | | | | | | Function Name | min | avg | median | max | # calls | @@ -88,63 +73,68 @@ | list(address,uint256,address,uint256,address)(uint256) | 139522 | 139522 | 139522 | 139522 | 5 | | listings | 1392 | 1392 | 1392 | 1392 | 15 | - -| src/Synthesizer.sol:Synthesizer contract | | | | | | -|------------------------------------------|-----------------|--------|--------|--------|---------| -| Deployment Cost | Deployment Size | | | | | -| 2774789 | 13966 | | | | | -| Function Name | min | avg | median | max | # calls | -| initialize | 92925 | 92925 | 92925 | 92925 | 14 | -| synthesizeIpnft | 13947 | 342258 | 373309 | 373309 | 16 | -| upgradeTo | 10306 | 10306 | 10306 | 10306 | 1 | - +| src/SignedMintAuthorizer.sol:SignedMintAuthorizer contract | | | | | | +| ---------------------------------------------------------- | --------------- | ---- | ------ | ---- | ------- | +| Deployment Cost | Deployment Size | | | | | +| 453372 | 2307 | | | | | +| Function Name | min | avg | median | max | # calls | +| authorizeMint | 7060 | 8260 | 9060 | 9060 | 5 | +| authorizeReservation | 351 | 351 | 351 | 351 | 2 | +| redeem | 690 | 690 | 690 | 690 | 2 | | src/TimelockedToken.sol:TimelockedToken contract | | | | | | -|--------------------------------------------------|-----------------|-------|--------|--------|---------| +| ------------------------------------------------ | --------------- | ----- | ------ | ------ | ------- | | Deployment Cost | Deployment Size | | | | | | 847083 | 4263 | | | | | | Function Name | min | avg | median | max | # calls | | approve | 408 | 408 | 408 | 408 | 1 | -| balanceOf | 598 | 985 | 598 | 2598 | 31 | -| initialize | 24568 | 24568 | 24568 | 24568 | 22 | -| lock | 629 | 87329 | 97693 | 136216 | 29 | -| release | 731 | 15258 | 23495 | 24295 | 8 | +| balanceOf | 598 | 973 | 598 | 2598 | 32 | +| initialize | 24568 | 24568 | 24568 | 24568 | 23 | +| lock | 629 | 87670 | 97693 | 136216 | 31 | +| release | 731 | 17098 | 24295 | 24458 | 10 | | releaseMany | 31359 | 31575 | 31575 | 31791 | 2 | | totalSupply | 363 | 363 | 363 | 363 | 3 | | transfer | 384 | 384 | 384 | 384 | 1 | +| src/Tokenizer.sol:Tokenizer contract | | | | | | +| ------------------------------------ | --------------- | ------ | ------ | ------ | ------- | +| Deployment Cost | Deployment Size | | | | | +| 2821010 | 14197 | | | | | +| Function Name | min | avg | median | max | # calls | +| initialize | 92925 | 92925 | 92925 | 92925 | 13 | +| proxiableUUID | 374 | 374 | 374 | 374 | 2 | +| reinit | 945 | 6958 | 9965 | 9965 | 3 | +| tokenizeIpnft | 13947 | 342041 | 373309 | 373309 | 16 | | src/crowdsale/CrowdSale.sol:CrowdSale contract | | | | | | -|------------------------------------------------|-----------------|--------|--------|--------|---------| +| ---------------------------------------------- | --------------- | ------ | ------ | ------ | ------- | | Deployment Cost | Deployment Size | | | | | | 1278414 | 6312 | | | | | | Function Name | min | avg | median | max | # calls | -| claim | 4725 | 19544 | 25007 | 31836 | 17 | +| claim | 4725 | 19848 | 25007 | 31836 | 18 | | claimResults | 507 | 28482 | 29579 | 48849 | 8 | | contribution | 720 | 720 | 720 | 720 | 2 | | getSaleInfo | 1282 | 1282 | 1282 | 1282 | 2 | -| placeBid | 494 | 46536 | 42946 | 84746 | 18 | +| placeBid | 494 | 45491 | 42946 | 84746 | 19 | | settle | 4114 | 12512 | 6872 | 25690 | 9 | | startSale | 482 | 117844 | 186298 | 192200 | 18 | - | src/crowdsale/LockingCrowdSale.sol:LockingCrowdSale contract | | | | | | -|--------------------------------------------------------------|-----------------|--------|--------|--------|---------| +| ------------------------------------------------------------ | --------------- | ------ | ------ | ------ | ------- | | Deployment Cost | Deployment Size | | | | | | 2509828 | 12417 | | | | | | Function Name | min | avg | median | max | # calls | -| claim | 9114 | 63874 | 70572 | 105612 | 5 | +| claim | 9114 | 67846 | 76028 | 125754 | 8 | | claimResults | 21705 | 34755 | 39106 | 39106 | 4 | | createOrReturnTimelockContract | 93249 | 93249 | 93249 | 93249 | 1 | | getSaleInfo | 1304 | 1304 | 1304 | 1304 | 4 | -| lockingContracts | 542 | 542 | 542 | 542 | 5 | -| placeBid | 84892 | 84892 | 84892 | 84892 | 5 | -| settle | 6039 | 28039 | 33540 | 33540 | 5 | -| startSale | 1228 | 241750 | 309338 | 309338 | 6 | - +| lockingContracts | 542 | 542 | 542 | 542 | 6 | +| placeBid | 43329 | 77794 | 84892 | 84892 | 7 | +| settle | 6039 | 28677 | 33540 | 33540 | 6 | +| startSale | 1228 | 250111 | 309338 | 309338 | 7 | | src/crowdsale/StakedLockingCrowdSale.sol:StakedLockingCrowdSale contract | | | | | | -|--------------------------------------------------------------------------------------------------------------|-----------------|--------|--------|--------|---------| +| ------------------------------------------------------------------------------------------------------------ | --------------- | ------ | ------ | ------ | ------- | | Deployment Cost | Deployment Size | | | | | | 3182692 | 15757 | | | | | | Function Name | min | avg | median | max | # calls | @@ -152,7 +142,7 @@ | claimResults | 21705 | 39536 | 39106 | 48882 | 10 | | getSaleInfo | 1304 | 1304 | 1304 | 1304 | 4 | | lockingContracts | 620 | 620 | 620 | 620 | 11 | -| placeBid | 11030 | 96630 | 85079 | 163938 | 25 | +| placeBid | 11032 | 96630 | 85079 | 163940 | 25 | | salesStaking | 790 | 790 | 790 | 790 | 1 | | settle | 5986 | 37518 | 33474 | 53374 | 11 | | stakesOf | 732 | 732 | 732 | 732 | 5 | @@ -161,5 +151,12 @@ | startSale((address,address,address,uint256,uint256,uint64,address),uint256)(uint256) | 472 | 472 | 472 | 472 | 1 | | trustVestingContract | 6350 | 24808 | 25984 | 27984 | 15 | +| src/helpers/test-upgrades/SynthPermissioner.sol:TermsAcceptedPermissioner contract | | | | | | +| ---------------------------------------------------------------------------------- | --------------- | ----- | ------ | ----- | ------- | +| Deployment Cost | Deployment Size | | | | | +| 602838 | 3043 | | | | | +| Function Name | min | avg | median | max | # calls | +| accept | 13657 | 14853 | 15053 | 15053 | 7 | +| specificTermsV1 | 3857 | 3857 | 3857 | 3857 | 5 | - +Ran 20 test suites: 104 tests passed, 0 failed, 0 skipped (104 total tests) diff --git a/.gas-snapshot b/.gas-snapshot index 451e6ff8..9dfde26c 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -6,7 +6,7 @@ BioPriceFeedTest:testSignalPrice() (gas: 66687) BioPriceFeedTest:testUSDC() (gas: 89912) ContractReceiverTest:testAbstractAccountsCanTradeIPNFTs() (gas: 1624822) ContractReceiverTest:testCanMintToERC721Receiver() (gas: 766329) -CrowdSaleFuzzTest:testFuzzManyBidders(uint8,uint96,uint96) (runs: 256, μ: 1004756, ~: 623920) +CrowdSaleFuzzTest:testFuzzManyBidders(uint8,uint96,uint96) (runs: 256, μ: 980846, ~: 623918) CrowdSaleLockedStakedTest:testCannotSetupCrowdSaleWithParentFunctions() (gas: 46156) CrowdSaleLockedStakedTest:testClaimLongAfterVestingPeriod() (gas: 1054349) CrowdSaleLockedStakedTest:testOverbiddingAndRefunds() (gas: 1429493) @@ -20,7 +20,7 @@ CrowdSaleLockedTest:testLockingCrowdSalesBadParameters() (gas: 44826) CrowdSaleLockedTest:testReusePubliclyCreatedLockingContracts() (gas: 696923) CrowdSaleLockedTest:testSettlementAndSimpleClaims() (gas: 585648) CrowdSaleLockedTest:testUnsuccessfulSaleClaims() (gas: 437742) -CrowdSalePermissionedTest:testPermissionedSettlementAndSimpleClaims() (gas: 1065294) +CrowdSalePermissionedTest:testPermissionedSettlementAndSimpleClaims() (gas: 1065304) CrowdSaleTest:testCannotCreateSaleWithoutFunds() (gas: 195937) CrowdSaleTest:testCannotInitializeSaleWithBadParams() (gas: 282101) CrowdSaleTest:testCreateSale() (gas: 273447) @@ -44,15 +44,15 @@ IPNFTTest:testCanWithdrawMintingFees() (gas: 211323) IPNFTTest:testCannotMintWhenPaused() (gas: 43473) IPNFTTest:testCannotSendPlainEtherToIPNFT() (gas: 32859) IPNFTTest:testInitialDeploymentState() (gas: 12565) -IPNFTTest:testMintFromReservation() (gas: 750032) +IPNFTTest:testMintFromReservation() (gas: 751086) IPNFTTest:testOnlyReservationOwnerCanMintFromReservation() (gas: 67821) IPNFTTest:testOwnerCanGrantReadAccess() (gas: 244202) IPNFTTest:testOwnerCanWithdrawEthFunds() (gas: 129262) IPNFTTest:testTokenReservation() (gas: 55471) IPNFTTest:testTokenReservationCounter() (gas: 82553) -IPNFTUpgrades:testFutureUpgrade() (gas: 6199733) -IPNFTUpgrades:testTokensSurviveUpgrade() (gas: 3598264) -IPNFTUpgrades:testUpgradeContract() (gas: 3143342) +IPNFTUpgrades:testFutureUpgrade() (gas: 6211380) +IPNFTUpgrades:testTokensSurviveUpgrade() (gas: 3609916) +IPNFTUpgrades:testUpgradeContract() (gas: 3154994) MintAuthorizerTest:testAuthorizerAcceptsOnlyValidSignatures() (gas: 47481) MintpassTest:testBatchMintFifty() (gas: 302226) MintpassTest:testBatchMintTen() (gas: 132039) @@ -65,12 +65,12 @@ MintpassTest:testRevokeToken() (gas: 145174) MintpassTest:testSafeMintFromNotOwner() (gas: 48811) MintpassTest:testSingleMints() (gas: 183497) MintpassTest:testTransfer() (gas: 91572) -PermissionerTest:testProveSigAndAcceptTerms() (gas: 78017) -PermissionerTest:testThatContractSignaturesAreAccepted() (gas: 4841563) +PermissionerTest:testProveSigAndAcceptTerms() (gas: 78025) +PermissionerTest:testThatContractSignaturesAreAccepted() (gas: 4841569) ReoverSigs:testRecoverManually() (gas: 7573) ReoverSigs:testRecoverOz() (gas: 9297) -SalesShareDistributorTest:testClaimBuyoutShares() (gas: 1559876) -SalesShareDistributorTest:testClaimBuyoutSharesAfterSwap() (gas: 1589727) +SalesShareDistributorTest:testClaimBuyoutShares() (gas: 1560084) +SalesShareDistributorTest:testClaimBuyoutSharesAfterSwap() (gas: 1589931) SalesShareDistributorTest:testClaimingFraud() (gas: 1720833) SalesShareDistributorTest:testCreateListingAndSell() (gas: 691331) SalesShareDistributorTest:testFuzzSynthesize(uint256,uint256) (runs: 256, μ: 616524, ~: 620087) @@ -89,15 +89,16 @@ SchmackoSwapTest:testGeneralBalancesAndSupplies() (gas: 36737) SchmackoSwapTest:testNonOwnerCannotCancelSale() (gas: 176543) SchmackoSwapTest:testNonOwnerCannotCreateSale() (gas: 41060) SchmackoSwapTest:testSellerCanManageAllowlist() (gas: 189957) -SynthesizerTest:testCanBeSynthesizedOnlyOnce() (gas: 652121) -SynthesizerTest:testCanUpgradeErc20TokenImplementation() (gas: 4023524) -SynthesizerTest:testCannotSynthesizeIfNotOwner() (gas: 37010) -SynthesizerTest:testGnosisSafeCanInteractWithMolecules() (gas: 4452256) -SynthesizerTest:testIncreaseMolecules() (gas: 491695) -SynthesizerTest:testIssueMolecules() (gas: 437075) -SynthesizerTest:testUrl() (gas: 464322) +SynthesizerUpgradeTest:testCanInteractWithUpgradedERC20sAfterCrowdsale() (gas: 7173426) +SynthesizerUpgradeTest:testCanUpgradeErc20TokenImplementation() (gas: 5165825) TimelockedTokenTest:testCanLock() (gas: 160134) TimelockedTokenTest:testCanWithdrawAfterLockingPeriod() (gas: 149737) TimelockedTokenTest:testCanWithdrawSeveralSchedules() (gas: 276242) TimelockedTokenTest:testCannotCreateTheSameSchedule() (gas: 151414) -TimelockedTokenTest:testCannotTransferOrApprove() (gas: 154940) \ No newline at end of file +TimelockedTokenTest:testCannotTransferOrApprove() (gas: 154940) +TokenizerTest:testCanBeTokenizedOnlyOnce() (gas: 652055) +TokenizerTest:testCannotTokenizeIfNotOwner() (gas: 36965) +TokenizerTest:testGnosisSafeCanInteractWithIPToken() (gas: 4452191) +TokenizerTest:testIncreaseIPToken() (gas: 491739) +TokenizerTest:testIssueIPToken() (gas: 437019) +TokenizerTest:testUrl() (gas: 464322) \ No newline at end of file diff --git a/SEQUENCE.md b/SEQUENCE.md index 08fa70a6..17a211f1 100644 --- a/SEQUENCE.md +++ b/SEQUENCE.md @@ -1,46 +1,46 @@ ```mermaid sequenceDiagram participant OO as OriginalOwner - participant Synthesizer - participant Molecules as MoleculesContract - participant MoleculeHolder + participant Tokenizer + participant IPToken as IPTokenContract + participant IPToken participant SOS as SchmackoSwap participant Buyer as IPNFTBuyer - OO->>Synthesizer: synthesizeIpnft() - Synthesizer->>Molecules: new Molecules instance - Molecules->>OO: issue initial amount + OO->>Tokenizer: tokenizeIpnft() + Tokenizer->>IPToken: new IPToken instance + IPToken->>OO: issue initial amount - OO->>MoleculeHolder: transfers molecules, e.g. by Crowdsale + OO->>IPToken: transfers IPToken, e.g. by Crowdsale - OO->>Molecules: issue() - Molecules->>OO: mints new tokens to OO + OO->>IPToken: issue() + IPToken->>OO: mints new tokens to OO - par sell IPNFT with Molecules as beneficiary + par sell IPNFT with IPToken as beneficiary OO->>SOS: approve all IPNFTs - OO->>SOS: list IPNFT for amt/USDC for SynthesizerContract + OO->>SOS: list IPNFT for amt/USDC for TokenizerContract Buyer->>SOS: pay list price amt - SOS->>Molecules: transfers payment funds + SOS->>IPToken: transfers payment funds OO->>Buyer: transfers IPNFT end alt sale via SchmackoSwap - OO->>Synthesizer: afterSale(listingId) - Note left of Synthesizer: can be called by any observer - Synthesizer->>SOS: check sales occurred with Synthesizer as beneficiary + OO->>Tokenizer: afterSale(listingId) + Note left of Tokenizer: can be called by any observer + Tokenizer->>SOS: check sales occurred with Tokenizer as beneficiary else custom sale - OO->>Synthesizer: afterSale(moleculesId, paymentToken, amount) - OO->>Molecules: transfers payment funds - Note left of Synthesizer: can only be called by the seller + OO->>Tokenizer: afterSale(IPTokenId, paymentToken, amount) + OO->>IPToken: transfers payment funds + Note left of Tokenizer: can only be called by the seller end - Synthesizer->>Synthesizer: start claiming phase + Tokenizer->>Tokenizer: start claiming phase - MoleculeHolder->>Molecules: burn(signature) - Molecules->>Synthesizer: verifies signature - Molecules->>Synthesizer: checks MoleculeHolder share amt - Molecules->>Molecules: burns all MoleculeHolder shares - Molecules->>MoleculeHolder: transfers share of payment token + IPToken->>IPToken: burn(signature) + IPToken->>Tokenizer: verifies signature + IPToken->>Tokenizer: checks IPToken share amt + IPToken->>IPToken: burns all IPToken shares + IPToken->>IPToken: transfers share of payment token ``` diff --git a/audit/TESTS.md b/audit/TESTS.md index 5d764d1b..11936809 100644 --- a/audit/TESTS.md +++ b/audit/TESTS.md @@ -29,20 +29,20 @@ yarn deploy:local ## Coverage -2023-07-10 +2023-07-28 | File | % Lines | % Statements | % Branches | % Funcs | | ---------------------------------------- | --------------- | --------------- | -------------- | -------------- | | src/BioPriceFeed.sol | 100.00% (4/4) | 100.00% (5/5) | 100.00% (0/0) | 100.00% (2/2) | -| src/IPNFT.sol | 77.50% (31/40) | 78.05% (32/41) | 85.71% (12/14) | 78.57% (11/14) | -| src/Mintpass.sol | 75.76% (25/33) | 77.14% (27/35) | 62.50% (10/16) | 78.57% (11/14) | -| src/Molecules.sol | 100.00% (14/14) | 100.00% (16/16) | 100.00% (2/2) | 100.00% (7/7) | -| src/Permissioner.sol | 87.50% (7/8) | 88.89% (8/9) | 100.00% (2/2) | 66.67% (4/6) | +| src/IPNFT.sol | 78.05% (32/41) | 77.27% (34/44) | 85.71% (12/14) | 78.57% (11/14) | +| src/IPToken.sol | 100.00% (14/14) | 100.00% (18/18) | 100.00% (2/2) | 100.00% (7/7) | +| src/Mintpass.sol | 75.76% (25/33) | 78.05% (32/41) | 62.50% (10/16) | 78.57% (11/14) | +| src/Permissioner.sol | 87.50% (7/8) | 90.91% (10/11) | 100.00% (2/2) | 83.33% (5/6) | | src/SalesShareDistributor.sol | 94.87% (37/39) | 95.56% (43/45) | 94.44% (17/18) | 71.43% (5/7) | -| src/SchmackoSwap.sol | 88.24% (30/34) | 81.40% (35/43) | 83.33% (15/18) | 75.00% (6/8) | +| src/SchmackoSwap.sol | 88.24% (30/34) | 80.00% (36/45) | 83.33% (15/18) | 75.00% (6/8) | | src/SignedMintAuthorizer.sol | 71.43% (5/7) | 80.00% (8/10) | 100.00% (0/0) | 75.00% (3/4) | -| src/Synthesizer.sol | 75.00% (12/16) | 77.78% (14/18) | 100.00% (4/4) | 66.67% (2/3) | -| src/TimelockedToken.sol | 82.76% (24/29) | 83.87% (26/31) | 100.00% (6/6) | 58.33% (7/12) | -| src/crowdsale/CrowdSale.sol | 98.81% (83/84) | 98.81% (83/84) | 95.00% (38/40) | 92.86% (13/14) | +| src/TimelockedToken.sol | 82.76% (24/29) | 76.47% (26/34) | 100.00% (6/6) | 58.33% (7/12) | +| src/Tokenizer.sol | 76.47% (13/17) | 78.95% (15/19) | 100.00% (4/4) | 50.00% (2/4) | +| src/crowdsale/CrowdSale.sol | 98.81% (83/84) | 98.82% (84/85) | 95.00% (38/40) | 92.86% (13/14) | | src/crowdsale/LockingCrowdSale.sol | 100.00% (24/24) | 100.00% (25/25) | 100.00% (6/6) | 100.00% (7/7) | | src/crowdsale/StakedLockingCrowdSale.sol | 94.23% (49/52) | 94.64% (53/56) | 88.89% (16/18) | 80.00% (8/10) | diff --git a/foundry.toml b/foundry.toml index b5768bc4..4f360277 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ libs = ['lib'] test = 'test' cache_path = 'cache_forge' solc_version = "0.8.18" -gas_reports = ["IPNFT", "IPNFTV2", "Mintpass", "SchmackoSwap", "Synthesizer", "Molecules", "CrowdSale", "LockingCrowdSale", "StakedLockingCrowdSale", "TimelockedToken"] +gas_reports = ["IPNFT", "IPNFTV2", "SchmackoSwap", "Tokenizer", "IPToken", "CrowdSale", "LockingCrowdSale", "StakedLockingCrowdSale", "TimelockedToken", "TermsAcceptedPermissioner", "SignedMintAuthorizer"] fs_permissions = [{ access = "read-write", path = "./SALEID.txt"}] [fmt] diff --git a/src/Permissioner.sol b/src/Permissioner.sol index 625bbd6e..fbb68031 100644 --- a/src/Permissioner.sol +++ b/src/Permissioner.sol @@ -75,7 +75,7 @@ contract TermsAcceptedPermissioner is IPermissioner { } /** - * @notice this yields the message text that claimers must present as signed message to burn their molecules and claim shares + * @notice this yields the message text that claimers must present to proof they have accepted all terms * @param tokenContract IPToken */ function specificTermsV1(IPToken tokenContract) public view returns (string memory) { diff --git a/subgraph/src/termsAcceptedPermissionerMapping.ts b/subgraph/src/termsAcceptedPermissionerMapping.ts index bb059aee..d59811f7 100644 --- a/subgraph/src/termsAcceptedPermissionerMapping.ts +++ b/subgraph/src/termsAcceptedPermissionerMapping.ts @@ -13,7 +13,7 @@ export function handleTermsAccepted(event: TermsAcceptedEvent): void { if (!iptBalance) { let reacted = IPT.load(event.params.tokenContract.toHexString()) if (!reacted) { - log.warning('molecules {} not found for signature', [balanceId]) + log.warning('IPT {} not found for signature', [balanceId]) } iptBalance = new IPTBalance(balanceId) iptBalance.owner = event.params.signer diff --git a/subgraph/src/tokenizerMapping.ts b/subgraph/src/tokenizerMapping.ts index 6227f714..91fb5c87 100644 --- a/subgraph/src/tokenizerMapping.ts +++ b/subgraph/src/tokenizerMapping.ts @@ -1,8 +1,5 @@ import { BigInt } from '@graphprotocol/graph-ts' -import { - MoleculesCreated as MoleculesCreatedEvent, - TokensCreated as TokensCreatedEvent -} from '../generated/Tokenizer/Tokenizer' +import { TokensCreated as TokensCreatedEvent } from '../generated/Tokenizer/Tokenizer' import { IPToken } from '../generated/templates' @@ -19,7 +16,7 @@ export function handleIPTsCreated(event: TokensCreatedEvent): void { reacted.name = event.params.name reacted.decimals = BigInt.fromU32(18) - //these will be updated by the underlying Molecules subgraph template + //these will be updated by the underlying IPT subgraph template reacted.totalIssued = BigInt.fromU32(0) reacted.circulatingSupply = BigInt.fromU32(0) IPToken.create(event.params.tokenContract) @@ -40,18 +37,3 @@ export function handleIPTsCreated(event: TokensCreatedEvent): void { // reacted.claimedShares = BigInt.fromI32(0); // reacted.save(); // } - -// export function handleTermsAccepted(event: TermsAcceptedEvent): void { -// let moleculesId = createMoleculesId( -// event.params.moleculesId, -// event.params.signer -// ); -// let molecule = Molecule.load(moleculesId); -// if (!molecule) { -// log.error('No molecules held by: {}', [moleculesId]); -// return; -// } -// molecule.agreementSigned = true; -// molecule.agreementSignature = event.params.signature; -// molecule.save(); -// } diff --git a/test/CrowdSale.t.sol b/test/CrowdSale.t.sol index 1763974e..19de98b7 100644 --- a/test/CrowdSale.t.sol +++ b/test/CrowdSale.t.sol @@ -37,7 +37,7 @@ contract CrowdSaleTest is Test { function setUp() public { crowdSale = new CrowdSale(); - auctionToken = new FakeERC20("MOLECULES","MOL"); + auctionToken = new FakeERC20("IPTOKENS","IPT"); biddingToken = new FakeERC20("USD token", "USDC"); auctionToken.mint(emitter, 500_000 ether); diff --git a/test/CrowdSaleFuzz.t.sol b/test/CrowdSaleFuzz.t.sol index 5e557d9d..e866da36 100644 --- a/test/CrowdSaleFuzz.t.sol +++ b/test/CrowdSaleFuzz.t.sol @@ -22,7 +22,7 @@ contract CrowdSaleFuzzTest is Test { function setUp() public { crowdSale = new CrowdSale(); - auctionToken = new FakeERC20("MOLECULES","MOL"); + auctionToken = new FakeERC20("IPTOKENS","IPT"); biddingToken = new FakeERC20("USD token", "USDC"); } diff --git a/test/CrowdSaleLockedStakedTest.t.sol b/test/CrowdSaleLockedStakedTest.t.sol index 849b1bd4..250128c8 100644 --- a/test/CrowdSaleLockedStakedTest.t.sol +++ b/test/CrowdSaleLockedStakedTest.t.sol @@ -46,7 +46,7 @@ contract CrowdSaleLockedStakedTest is Test { function setUp() public { vm.startPrank(deployer); - auctionToken = new FakeERC20("MOLECULES","MOL"); + auctionToken = new FakeERC20("IPTOKENS","IPT"); auctionToken.mint(emitter, 500_000 ether); biddingToken = new FakeERC20("USD token", "USDC"); daoToken = new FakeERC20("DAO token", "DAO"); diff --git a/test/CrowdSaleLockedTest.t.sol b/test/CrowdSaleLockedTest.t.sol index e3e9da2a..0207ea9f 100644 --- a/test/CrowdSaleLockedTest.t.sol +++ b/test/CrowdSaleLockedTest.t.sol @@ -26,7 +26,7 @@ contract CrowdSaleLockedTest is Test { function setUp() public { crowdSale = new LockingCrowdSale(); - auctionToken = new FakeERC20("MOLECULES","MOL"); + auctionToken = new FakeERC20("IPTOKENS","IPT"); biddingToken = new FakeERC20("USD token", "USDC"); auctionToken.mint(emitter, 500_000 ether); diff --git a/test/CrowdSalePermissioned.t.sol b/test/CrowdSalePermissioned.t.sol index b3914a1b..df884f0a 100644 --- a/test/CrowdSalePermissioned.t.sol +++ b/test/CrowdSalePermissioned.t.sol @@ -44,7 +44,7 @@ contract CrowdSalePermissionedTest is Test { vm.startPrank(deployer); auctionToken = new IPToken(); - auctionToken.initialize("MOLECULES", "MOL-0001", Metadata(42, msg.sender, "ipfs://abcde")); + auctionToken.initialize("IPTOKENS", "IPT-0001", Metadata(42, msg.sender, "ipfs://abcde")); biddingToken = new FakeERC20("USD token", "USDC"); daoToken = new FakeERC20("DAO token", "DAO"); diff --git a/test/CrowdSaleWithNonStandardERC20Test.t.sol b/test/CrowdSaleWithNonStandardERC20Test.t.sol index 576d2a6e..0d6873dd 100644 --- a/test/CrowdSaleWithNonStandardERC20Test.t.sol +++ b/test/CrowdSaleWithNonStandardERC20Test.t.sol @@ -40,7 +40,7 @@ contract CrowdSaleWithNonStandardERC20Test is Test { function setUp() public { vm.startPrank(deployer); - auctionToken = new FakeERC20("MOLECULES","MOL"); + auctionToken = new FakeERC20("IPTOKENS","IPT"); biddingToken = new FakeERC20("USD token", "USDC"); biddingToken.setDecimals(6); diff --git a/test/IPNFTMintHelper.sol b/test/IPNFTMintHelper.sol index 3d932730..f9732ebe 100644 --- a/test/IPNFTMintHelper.sol +++ b/test/IPNFTMintHelper.sol @@ -15,7 +15,7 @@ abstract contract IPNFTMintHelper is Test { address deployer = makeAddr("chucknorris"); uint256 constant MINTING_FEE = 0.001 ether; - string DEFAULT_SYMBOL = "MOL-0001"; + string DEFAULT_SYMBOL = "IPT-0001"; function reserveAToken(IReservable ipnft, address to) internal returns (uint256) { vm.startPrank(to); diff --git a/test/SchmackoSwap.t.sol b/test/SchmackoSwap.t.sol index d5c072bb..947299e0 100644 --- a/test/SchmackoSwap.t.sol +++ b/test/SchmackoSwap.t.sol @@ -16,7 +16,7 @@ import { FakeERC20 } from "../src/helpers/FakeERC20.sol"; contract SchmackoSwapTest is Test { string arUri = "ar://tNbdHqh3AVDHVD06P0OPUXSProI5kGcZZw8IvLkekSY"; uint256 constant MINTING_FEE = 0.001 ether; - string DEFAULT_SYMBOL = "MOL-0001"; + string DEFAULT_SYMBOL = "IPT-0001"; IPNFT internal ipnft; IERC721 internal ierc721; diff --git a/test/Tokenizer.t.sol b/test/Tokenizer.t.sol index 4a1a0179..639f3ccf 100644 --- a/test/Tokenizer.t.sol +++ b/test/Tokenizer.t.sol @@ -32,7 +32,7 @@ contract TokenizerTest is Test { string ipfsUri = "ipfs://bafkreiankqd3jvpzso6khstnaoxovtyezyatxdy7t2qzjoolqhltmasqki"; string agreementCid = "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq"; uint256 MINTING_FEE = 0.001 ether; - string DEFAULT_SYMBOL = "MOL-0001"; + string DEFAULT_SYMBOL = "IPT-0001"; address deployer = makeAddr("chucknorris"); address protocolOwner = makeAddr("protocolOwner"); @@ -84,7 +84,7 @@ contract TokenizerTest is Test { function testUrl() public { vm.startPrank(originalOwner); - IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, ""); string memory uri = tokenContract.uri(); assertGt(bytes(uri).length, 200); vm.stopPrank(); @@ -93,7 +93,7 @@ contract TokenizerTest is Test { function testIssueIPToken() public { vm.startPrank(originalOwner); //ipnft.setApprovalForAll(address(tokenizer), true); - IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, ""); vm.stopPrank(); assertEq(tokenContract.balanceOf(originalOwner), 100_000); @@ -101,7 +101,7 @@ contract TokenizerTest is Test { assertEq(ipnft.ownerOf(1), originalOwner); assertEq(tokenContract.totalIssued(), 100_000); - assertEq(tokenContract.symbol(), "MOLE"); + assertEq(tokenContract.symbol(), "IPT"); vm.startPrank(originalOwner); tokenContract.transfer(alice, 10_000); @@ -114,7 +114,7 @@ contract TokenizerTest is Test { function testIncreaseIPToken() public { vm.startPrank(originalOwner); - IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, ""); tokenContract.transfer(alice, 25_000); tokenContract.transfer(bob, 25_000); @@ -147,10 +147,10 @@ contract TokenizerTest is Test { function testCanBeTokenizedOnlyOnce() public { vm.startPrank(originalOwner); - tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, ""); vm.expectRevert(AlreadyTokenized.selector); - tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, ""); vm.stopPrank(); } @@ -158,7 +158,7 @@ contract TokenizerTest is Test { vm.startPrank(alice); vm.expectRevert(MustOwnIpnft.selector); - tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, ""); vm.stopPrank(); } @@ -174,7 +174,7 @@ contract TokenizerTest is Test { vm.stopPrank(); vm.startPrank(originalOwner); - IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, ""); tokenContract.safeTransfer(address(wallet), 100_000); vm.stopPrank(); @@ -203,7 +203,7 @@ contract TokenizerTest is Test { // vm.stopPrank(); // vm.startPrank(originalOwner); - // IPToken tokenContractOld = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, ""); + // IPToken tokenContractOld = tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, ""); // vm.stopPrank(); // vm.startPrank(deployer);