diff --git a/README.md b/README.md
index 2ea98796..425fbbc2 100644
--- a/README.md
+++ b/README.md
@@ -87,38 +87,12 @@ VDAO_TOKEN_ADDRESS=0x19A3036b828bffB5E14da2659E950E76f8e6BAA2
---
-### ~~Deprecated Goerli~~
+### upgrading to Tokenizer 1.3
-| Contract | Address | Actions |
-| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| IP-NFT | [0xaf7358576C9F7cD84696D28702fC5ADe33cce0e9](https://goerli.etherscan.io/address/0xaf7358576C9F7cD84696D28702fC5ADe33cce0e9#code>) | |
-| SchmackoSwap | [0x67D8ed102E2168A46FA342e39A5f7D16c103Bd0d](https://goerli.etherscan.io/address/0x67D8ed102E2168A46FA342e39A5f7D16c103Bd0d#code) | |
-| Tokenizer | [0xb12494eeA6B992d0A1Db3C5423BE7a2d2337F58c](https://goerli.etherscan.io/address/0xb12494eeA6B992d0A1Db3C5423BE7a2d2337F58c#code) | |
-| Permissioner | [0xd735d9504cce32F2cd665b779D699B4157686fcd](https://goerli.etherscan.io/address/0xd735d9504cce32F2cd665b779D699B4157686fcd#code) | |
-| Crowdsale | [0x8c83DA72b4591bE526ca8C7cb848bC89c0e23373](https://goerli.etherscan.io/address/0x8c83DA72b4591bE526ca8C7cb848bC89c0e23373#code>) | |
-| StakedLockingCrowdSale | [0x46c3369dece07176ad7164906d3593aa4c126d35](https://goerli.etherscan.io/address/0x46c3369dece07176ad7164906d3593aa4c126d35#code) | |
-| SignedMintAuthorizer | [0x5e555eE24DB66825171Ac63EA614864987CEf1Af](https://goerli.etherscan.io/address/0x5e555eE24DB66825171Ac63EA614864987CEf1Af#code) | |
-| IPToken Implementation | [0x38Ca0fEEc7cd48629f9388727bfA747859a6feE7](https://goerli.etherscan.io/address/0x38Ca0fEEc7cd48629f9388727bfA747859a6feE7#code) | |
+forge script --private-key=$PRIVATE_KEY --rpc-url=$RPC_URL script/prod/RolloutTokenizerV13.s.sol --broadcast
-- Subgraph: https://api.thegraph.com/subgraphs/name/moleculeprotocol/ip-nft-goerli
-
-- Tokenizer Implementation 1.2: 0x18E5ae026CFC8020b2eDbA7050eA6144Fd313c02 (reinit 4)
-
-- Bio pricefeed: 0x8647dEFdEAAdF5448d021B364B2F17815aba4360
-
-
-- old ("molecule") permissioner: 0x0045723801561079d94f0Bb1B65f322078E52635
-
-
-- Blind Permissioner: 0xec68a1fc8d4c2834f8dfbdb56691f9f0a3d6be11
-
-
-#### ~~Tokens~~
-
-| Token name | Symbol | address |
-| ------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
-| BioDao Test token | BIODAO | [0x3110a768DC64f7aAB92F7Ae6E1371e5CE581F95F](https://goerli.etherscan.io/address/0x3110a768dc64f7aab92f7ae6e1371e5ce581f95f#code) |
-| Vested BioDao Test token | vBIODAO | [0x6FFBd6325B2102F5f9AaB74d7418A27F9174c92f](https://goerli.etherscan.io/address/0x6FFBd6325B2102F5f9AaB74d7418A27F9174c92f) |
+// 0xTokenizer 0xNewImpl 0xNewTokenImpl
+cast send --rpc-url=$RPC_URL --private-key=$PRIVATE_KEY 0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e "upgradeToAndCall(address,bytes)" 0x70e0bA845a1A0F2DA3359C97E0285013525FFC49 0x84646c1f000000000000000000000000998abeb3e57409262ae5b751f60747921b33613e
## Prerequisites
diff --git a/deploy/inittest.sh b/deploy/inittest.sh
index c2fec7b3..1cb4a98e 100755
--- a/deploy/inittest.sh
+++ b/deploy/inittest.sh
@@ -14,10 +14,14 @@ $DC ps
./setupLocal.sh -f
cd subgraph
-yarn prepare:local
yarn codegen
+yarn build:local
yarn create:local
+# that's a bad local config hack still required: the root's subgraph.yaml's network must be "mainnet" for foundry
+sed -i '' -e 's/network\: foundry/network\: mainnet/g' build/subgraph.yaml
+sed -i '' -e 's/network\: foundry/network\: mainnet/g' subgraph.yaml
yarn deploy:local -l v0.0.1
+sed -i '' -e 's/network\: mainnet/network\: foundry/g' subgraph.yaml
cd ..
$DC exec -T postgres pg_dump -Fc -U graph-node -w graph-node -f after_setup.dump
diff --git a/docker-compose.yml b/docker-compose.yml
index 35937f55..259afdeb 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,15 +1,14 @@
#https://github.com/graphprotocol/graph-node/blob/master/docker/docker-compose.yml
-version: '3'
services:
anvil:
- image: ghcr.io/foundry-rs/foundry:nightly-a117fbfa41edbaa1618ed099d78d65727bff4790
+ image: ghcr.io/foundry-rs/foundry:nightly-a470d635cfcdce68609e9dc5762a3584351bacc1
command:
- 'anvil --host 0.0.0.0'
ports:
- '8545:8545'
graph-node:
- image: graphprotocol/graph-node
+ image: graphprotocol/graph-node:845f8fa
ports:
- '8000:8000'
- '8001:8001'
@@ -19,6 +18,7 @@ services:
depends_on:
- ipfs
- postgres
+ - anvil
extra_hosts:
- host.docker.internal:host-gateway
environment:
@@ -28,7 +28,7 @@ services:
postgres_db: graph-node
ipfs: 'ipfs:5001'
ethereum: 'mainnet:http://anvil:8545'
- GRAPH_LOG: info
+ GRAPH_LOG: debug
GRAPH_ALLOW_NON_DETERMINISTIC_IPFS: 1
ipfs:
image: ipfs/kubo:v0.28.0
diff --git a/package.json b/package.json
index b4dc992b..fadfd480 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,8 @@
{
"license": "MIT",
"scripts": {
- "test": "hardhat test --network hardhat"
+ "test": "hardhat test --network hardhat",
+ "clean": "rm -rf out cache_forge"
},
"devDependencies": {
"@nomicfoundation/hardhat-foundry": "^1.0.0",
diff --git a/script/dev/Synthesizer.s.sol b/script/dev/Synthesizer.s.sol
index 0d57b939..eb8e8627 100644
--- a/script/dev/Synthesizer.s.sol
+++ b/script/dev/Synthesizer.s.sol
@@ -34,13 +34,7 @@ contract DeploySynthesizer is CommonScript {
function run() public {
prepareAddresses();
vm.startBroadcast(deployer);
- Synthesizer synthesizer = Synthesizer(
- address(
- new ERC1967Proxy(
- address(new Synthesizer()), ""
- )
- )
- );
+ Synthesizer synthesizer = Synthesizer(address(new ERC1967Proxy(address(new Synthesizer()), "")));
MolTermsAcceptedPermissioner oldPermissioner = new MolTermsAcceptedPermissioner();
synthesizer.initialize(IPNFT(vm.envAddress("IPNFT_ADDRESS")), oldPermissioner);
@@ -97,7 +91,7 @@ contract UpgradeSynthesizerToTokenizer is CommonScript {
Tokenizer tokenizer = Tokenizer(address(synthesizer));
TermsAcceptedPermissioner newTermsPermissioner = new TermsAcceptedPermissioner();
- tokenizer.reinit(newTermsPermissioner);
+ //todo tokenizer.reinit(newTermsPermissioner);
vm.stopBroadcast();
console.log("TOKENIZER_ADDRESS=%s", address(tokenizer)); //should equal synthesizer
diff --git a/script/dev/Tokenizer.s.sol b/script/dev/Tokenizer.s.sol
index 98630a55..96684539 100644
--- a/script/dev/Tokenizer.s.sol
+++ b/script/dev/Tokenizer.s.sol
@@ -58,6 +58,5 @@ contract FixtureTokenizer is CommonScript {
vm.stopBroadcast();
console.log("IPTS_ADDRESS=%s", address(tokenContract));
- console.log("IPT round hash: %s", tokenContract.hash());
}
}
diff --git a/script/prod/RolloutTokenizerV12.s.sol b/script/prod/RolloutTokenizerV13.s.sol
similarity index 85%
rename from script/prod/RolloutTokenizerV12.s.sol
rename to script/prod/RolloutTokenizerV13.s.sol
index 5bcbb1f9..35aa5f4e 100644
--- a/script/prod/RolloutTokenizerV12.s.sol
+++ b/script/prod/RolloutTokenizerV13.s.sol
@@ -6,14 +6,14 @@ import { Tokenizer } from "../../src/Tokenizer.sol";
import { IPToken } from "../../src/IPToken.sol";
import { console } from "forge-std/console.sol";
-contract RolloutTokenizerV12 is Script {
+contract RolloutTokenizerV13 is Script {
function run() public {
vm.startBroadcast();
IPToken newIpTokenImplementation = new IPToken();
Tokenizer newTokenizerImplementation = new Tokenizer();
- bytes memory upgradeCallData = abi.encodeWithSelector(Tokenizer.setIPTokenImplementation.selector, address(newIpTokenImplementation));
+ bytes memory upgradeCallData = abi.encodeWithSelector(Tokenizer.reinit.selector, address(newIpTokenImplementation));
console.log("NEWTOKENIMPLEMENTATION=%s", address(newIpTokenImplementation));
console.log("NEWTOKENIZER=%s", address(newTokenizerImplementation));
diff --git a/src/IControlIPTs.sol b/src/IControlIPTs.sol
new file mode 100644
index 00000000..86c53443
--- /dev/null
+++ b/src/IControlIPTs.sol
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.18;
+
+/**
+ * @title IControlIPTs 1.3
+ * @author molecule.xyz
+ * @notice must be implemented by contracts that should control IPTs
+ */
+interface IControlIPTs {
+ function controllerOf(uint256 ipnftId) external view returns (address);
+}
diff --git a/src/IPToken.sol b/src/IPToken.sol
index 9dcc984d..84a0d19f 100644
--- a/src/IPToken.sol
+++ b/src/IPToken.sol
@@ -5,6 +5,8 @@ import { ERC20BurnableUpgradeable } from "@openzeppelin/contracts-upgradeable/to
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";
+import { Tokenizer, MustControlIpnft } from "./Tokenizer.sol";
+import { IControlIPTs } from "./IControlIPTs.sol";
struct Metadata {
uint256 ipnftId;
@@ -13,67 +15,59 @@ struct Metadata {
}
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.
+ * @title IPToken 1.3
+ * @author molecule.xyz
+ * @notice this is a template contract that's cloned by the Tokenizer
+ * @notice the owner of this contract is always the Tokenizer contract which enforces IPNFT holdership rules.
+ * The owner can increase the token supply as long as it's not explicitly capped.
* @dev formerly known as "molecules"
*/
contract IPToken is ERC20BurnableUpgradeable, OwnableUpgradeable {
event Capped(uint256 atSupply);
- //this will only go up.
+ /// @notice the amount of tokens that ever have been issued (not necessarily == supply)
uint256 public totalIssued;
- /**
- * @notice when true, no one can ever mint tokens again.
- */
+
+ /// @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 {
+ function initialize(uint256 ipnftId, string calldata name, string calldata symbol, address originalOwner, string memory agreementCid)
+ external
+ initializer
+ {
__Ownable_init();
__ERC20_init(name, symbol);
- _metadata = metadata_;
+ _metadata = Metadata({ ipnftId: ipnftId, originalOwner: originalOwner, agreementCid: agreementCid });
}
constructor() {
_disableInitializers();
}
- modifier onlyIssuerOrOwner() {
- if (_msgSender() != _metadata.originalOwner && _msgSender() != owner()) {
- revert OnlyIssuerOrOwner();
+ modifier onlyTokenizerOrIPNFTController() {
+ if (_msgSender() != owner() && _msgSender() != IControlIPTs(owner()).controllerOf(_metadata.ipnftId)) {
+ revert MustControlIpnft();
}
_;
}
- 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
+ * @notice the supply of IP Tokens is controlled by the tokenizer contract.
* @param receiver address
* @param amount uint256
*/
- function issue(address receiver, uint256 amount) external onlyIssuerOrOwner {
- if (capped) revert TokenCapped();
+ function issue(address receiver, uint256 amount) external onlyTokenizerOrIPNFTController {
+ if (capped) {
+ revert TokenCapped();
+ }
totalIssued += amount;
_mint(receiver, amount);
}
@@ -81,7 +75,7 @@ contract IPToken is ERC20BurnableUpgradeable, OwnableUpgradeable {
/**
* @notice mark this token as capped. After calling this, no new tokens can be `issue`d
*/
- function cap() external onlyIssuerOrOwner {
+ function cap() external onlyTokenizerOrIPNFTController {
capped = true;
emit Capped(totalIssued);
}
diff --git a/src/SalesShareDistributor.sol b/src/SalesShareDistributor.sol
index 1cd3c1df..16208780 100644
--- a/src/SalesShareDistributor.sol
+++ b/src/SalesShareDistributor.sol
@@ -7,7 +7,9 @@ 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 { IPNFT } from "./IPNFT.sol";
import { IPToken, Metadata } from "./IPToken.sol";
+import { Tokenizer, MustControlIpnft } from "./Tokenizer.sol";
import { SchmackoSwap, ListingState } from "./SchmackoSwap.sol";
import { IPermissioner, TermsAcceptedPermissioner } from "./Permissioner.sol";
@@ -21,11 +23,17 @@ struct Sales {
error ListingNotFulfilled();
error ListingMismatch();
error InsufficientBalance();
+error NotSalesBeneficiary();
error UncappedToken();
-error OnlyIssuer();
-
+error OnlySeller();
error NotClaimingYet();
+/**
+ * @title SalesShareDistributor
+ * @author molecule.xyz
+ * @notice THIS IS NOT SAFE TO BE USED IN PRODUCTION!!
+ * This is a one time sell out contract for a "final" IPT sale and requires the IP token to be capped.
+ */
contract SalesShareDistributor is UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable {
using SafeERC20 for IERC20;
@@ -41,7 +49,7 @@ contract SalesShareDistributor is UUPSUpgradeable, OwnableUpgradeable, Reentranc
}
/**
- * @notice returns the `amount` of `paymentToken` that `tokenHolder` can claim by burning their molecules
+ * @notice returns the `amount` of `paymentToken` that `tokenHolder` can claim by burning their IP Tokens
*
* @param tokenContract address
* @param holder address
@@ -84,64 +92,69 @@ contract SalesShareDistributor is UUPSUpgradeable, OwnableUpgradeable, Reentranc
}
/**
- * @notice anyone should be able to call this function after having observed the sale
- * 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
+ * @notice release sales shares for a Schmackoswap transaction
+ * @dev todo: *anyone* should be able to call this function after having observed the sale; right now we restrict it to the creator of the trade since they were in control of the IPNFT before
+ * @dev this has a deep dependency on our own swap contract
*
- * @param tokenContract IPToken the tokenContract of the IPToken
+ * @param ipt IPToken the tokenContract of the IPToken
* @param listingId uint256 the listing id on Schmackoswap
* @param permissioner IPermissioner the permissioner that permits claims
*/
- function afterSale(IPToken tokenContract, uint256 listingId, IPermissioner permissioner) external {
- if (_msgSender() != tokenContract.issuer()) {
- revert OnlyIssuer();
- }
-
- Metadata memory metadata = tokenContract.metadata();
- (, uint256 ipnftId,, IERC20 _paymentToken, uint256 askPrice, address beneficiary, ListingState listingState) =
+ function afterSale(IPToken ipt, uint256 listingId, IPermissioner permissioner) external {
+ (, uint256 ipnftId, address seller, IERC20 _paymentToken, uint256 askPrice, address beneficiary, ListingState listingState) =
schmackoSwap.listings(listingId);
+ if (_msgSender() != seller) {
+ revert OnlySeller();
+ }
+
if (listingState != ListingState.FULFILLED) {
revert ListingNotFulfilled();
}
+ Metadata memory metadata = ipt.metadata();
if (ipnftId != metadata.ipnftId) {
revert ListingMismatch();
}
if (beneficiary != address(this)) {
- revert InsufficientBalance();
+ revert NotSalesBeneficiary();
}
- _startClaimingPhase(tokenContract, listingId, _paymentToken, askPrice, permissioner);
+ _startClaimingPhase(ipt, listingId, _paymentToken, askPrice, permissioner);
}
//audit: ensure that no one can withdraw arbitrary amounts here
//by simply creating a new IPToken instance and claim an arbitrary value
-
+ //todo: try breaking this by providing a fake IPT with a fake Tokenizer owner
+ //todo: this must be called by the beneficiary of a sale we don't control.
/**
* @notice When the sales beneficiary has not been set to the underlying erc20 token address but to the original owner's wallet instead,
- * they can invoke this method to start the claiming phase manually. This e.g. allows sales off the record.
+ * they can invoke this method to start the claiming phase manually. This e.g. allows sales off the record ("OpenSea").
*
- * Requires the originalOwner to behave honestly / in favor of the molecules holders
- * Requires the caller to have approved `price` of `paymentToken` to this contract
+ * Requires the originalOwner to behave honestly / in favor of the IPT holders
+ * Requires the caller to have approved `paidPrice` of `paymentToken` to this 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
+ * @param paidPrice uint256 the price the NFT has been sold for
+ * @param permissioner IPermissioner the permissioner that permits claims
*/
- function afterSale(IPToken tokenContract, IERC20 paymentToken, uint256 paidPrice, IPermissioner permissioner) external nonReentrant {
- if (_msgSender() != tokenContract.issuer()) {
- revert OnlyIssuer();
- }
-
+ function UNSAFE_afterSale(IPToken tokenContract, IERC20 paymentToken, uint256 paidPrice, IPermissioner permissioner) external nonReentrant {
Metadata memory metadata = tokenContract.metadata();
+ Tokenizer tokenizer = Tokenizer(tokenContract.owner());
+
+ //todo: this should be a selected beneficiary of the IPNFT's sales proceeds, and not the original owner :)
+ //idea is to allow *several* sales proceeds to be notified here, create unique sales ids for each and let users claim the all of them at once
+ if (_msgSender() != metadata.originalOwner) {
+ revert MustControlIpnft();
+ }
+
//create a fake (but valid) schmackoswap listing id
uint256 fulfilledListingId = uint256(
keccak256(
abi.encode(
SchmackoSwap.Listing(
- IERC721(address(0)), //this should be the IPNFT address
+ IERC721(address(tokenizer.getIPNFTContract())),
metadata.ipnftId,
_msgSender(),
paymentToken,
@@ -157,14 +170,13 @@ contract SalesShareDistributor is UUPSUpgradeable, OwnableUpgradeable, Reentranc
paymentToken.safeTransferFrom(_msgSender(), address(this), paidPrice);
}
- function _startClaimingPhase(IPToken tokenContract, uint256 fulfilledListingId, IERC20 _paymentToken, uint256 price, IPermissioner permissioner)
- internal
- {
- if (!tokenContract.capped()) {
- revert UncappedToken();
- }
- sales[address(tokenContract)] = Sales(fulfilledListingId, _paymentToken, price, permissioner);
- emit SalesActivated(address(tokenContract), address(_paymentToken), price);
+ function _startClaimingPhase(IPToken ipt, uint256 fulfilledListingId, IERC20 _paymentToken, uint256 price, IPermissioner permissioner) internal {
+ //todo: this *should* be enforced before a sale starts
+ // if (!tokenContract.capped()) {
+ // revert UncappedToken();
+ // }
+ sales[address(ipt)] = Sales(fulfilledListingId, _paymentToken, price, permissioner);
+ emit SalesActivated(address(ipt), address(_paymentToken), price);
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner { }
diff --git a/src/Tokenizer.sol b/src/Tokenizer.sol
index 56bb70b1..d06586db 100644
--- a/src/Tokenizer.sol
+++ b/src/Tokenizer.sol
@@ -8,15 +8,17 @@ import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { IPToken, Metadata as TokenMetadata } from "./IPToken.sol";
import { IPermissioner } from "./Permissioner.sol";
import { IPNFT } from "./IPNFT.sol";
+import { IControlIPTs } from "./IControlIPTs.sol";
-error MustOwnIpnft();
+error MustControlIpnft();
error AlreadyTokenized();
error ZeroAddress();
+error IPTNotControlledByTokenizer();
-/// @title Tokenizer 1.2
+/// @title Tokenizer 1.3
/// @author molecule.to
/// @notice tokenizes an IPNFT to an ERC20 token (called IPToken or IPT) and controls its supply.
-contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
+contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable, IControlIPTs {
event TokensCreated(
uint256 indexed moleculesId,
uint256 indexed ipnftId,
@@ -43,7 +45,7 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
/// @dev the permissioner checks if senders have agreed to legal requirements
IPermissioner public permissioner;
- /// @notice the IPToken implementation this Tokenizer spawns
+ /// @notice the IPToken implementation this Tokenizer clones from
IPToken public ipTokenImplementation;
/**
@@ -63,11 +65,29 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
_disableInitializers();
}
+ function getIPNFTContract() public view returns (IPNFT) {
+ return ipnft;
+ }
+
+ //todo: try breaking this with a faked IPToken
+ modifier onlyController(IPToken ipToken) {
+ TokenMetadata memory metadata = ipToken.metadata();
+
+ if (address(synthesized[metadata.ipnftId]) != address(ipToken)) {
+ revert IPTNotControlledByTokenizer();
+ }
+
+ if (_msgSender() != controllerOf(metadata.ipnftId)) {
+ revert MustControlIpnft();
+ }
+ _;
+ }
+
/**
* @notice sets the new implementation address of the IPToken
* @param _ipTokenImplementation address pointing to the new implementation
*/
- function setIPTokenImplementation(IPToken _ipTokenImplementation) external onlyOwner {
+ function setIPTokenImplementation(IPToken _ipTokenImplementation) public onlyOwner {
/*
could call some functions on old contract to make sure its tokenizer not another contract behind a proxy for safety
*/
@@ -80,16 +100,18 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
}
/**
- * @dev called after an upgrade to reinitialize a new permissioner impl.
- * @param _permissioner the new TermsPermissioner
+ * @dev sets legacy IPTs on the tokenized mapping
*/
- function reinit(IPermissioner _permissioner) public onlyOwner reinitializer(4) {
- permissioner = _permissioner;
+ function reinit(IPToken _ipTokenImplementation) public onlyOwner reinitializer(5) {
+ synthesized[2] = IPToken(0x6034e0d6999741f07cb6Fb1162cBAA46a1D33d36);
+ synthesized[28] = IPToken(0x7b66E84Be78772a3afAF5ba8c1993a1B5D05F9C2);
+ synthesized[37] = IPToken(0xBcE56276591128047313e64744b3EBE03998783f);
+
+ setIPTokenImplementation(_ipTokenImplementation);
}
/**
- * @notice initializes synthesis on ipnft#id for the current asset holder.
- * IPTokens are identified by the original token holder and the token id
+ * @notice tokenizes ipnft#id for the current asset holder.
* @param ipnftId the token id on the underlying nft collection
* @param tokenAmount the initially issued supply of IP tokens
* @param tokenSymbol the ip token's ticker symbol
@@ -104,29 +126,60 @@ contract Tokenizer is UUPSUpgradeable, OwnableUpgradeable {
string memory agreementCid,
bytes calldata signedAgreement
) external returns (IPToken token) {
- if (ipnft.ownerOf(ipnftId) != _msgSender()) {
- revert MustOwnIpnft();
+ if (_msgSender() != controllerOf(ipnftId)) {
+ revert MustControlIpnft();
+ }
+ if (address(synthesized[ipnftId]) != address(0)) {
+ revert AlreadyTokenized();
}
// https://github.com/OpenZeppelin/workshops/tree/master/02-contracts-clone
token = IPToken(Clones.clone(address(ipTokenImplementation)));
string memory name = string.concat("IP Tokens of IPNFT #", Strings.toString(ipnftId));
- token.initialize(name, tokenSymbol, TokenMetadata(ipnftId, _msgSender(), agreementCid));
+ token.initialize(ipnftId, name, tokenSymbol, _msgSender(), agreementCid);
- uint256 tokenHash = token.hash();
- // ensure we can only call this once per sales cycle
- if (address(synthesized[tokenHash]) != address(0)) {
- revert AlreadyTokenized();
- }
-
- synthesized[tokenHash] = token;
+ synthesized[ipnftId] = token;
//this has been called MoleculesCreated before
- emit TokensCreated(tokenHash, ipnftId, address(token), _msgSender(), tokenAmount, agreementCid, name, tokenSymbol);
+ emit TokensCreated(
+ //upwards compatibility: signaling a unique "Molecules ID" as first parameter ("sales cycle id"). This is unused and not interpreted.
+ uint256(keccak256(abi.encodePacked(ipnftId))),
+ ipnftId,
+ address(token),
+ _msgSender(),
+ tokenAmount,
+ agreementCid,
+ name,
+ tokenSymbol
+ );
permissioner.accept(token, _msgSender(), signedAgreement);
token.issue(_msgSender(), tokenAmount);
}
+ /**
+ * @notice issues more IPTs when not capped. This can be used for new owners of legacy IPTs that otherwise wouldn't be able to pass their `onlyIssuerOrOwner` gate
+ * @param ipToken The ip token to control
+ * @param amount the amount of tokens to issue
+ * @param receiver the address that receives the tokens
+ */
+ function issue(IPToken ipToken, uint256 amount, address receiver) external onlyController(ipToken) {
+ ipToken.issue(receiver, amount);
+ }
+
+ /**
+ * @notice caps the supply of an IPT. After calling this, no new tokens can be `issue`d
+ * @dev you must compute the ipt hash externally.
+ * @param ipToken the IPToken to cap.
+ */
+ function cap(IPToken ipToken) external onlyController(ipToken) {
+ ipToken.cap();
+ }
+
+ /// @dev this will be called by IPTs. Right now the controller is the IPNFT's current owner, it can be a Governor in the future.
+ function controllerOf(uint256 ipnftId) public view override returns (address) {
+ return ipnft.ownerOf(ipnftId);
+ }
+
/// @notice upgrade authorization logic
function _authorizeUpgrade(address /*newImplementation*/ )
internal
diff --git a/src/helpers/test-upgrades/IPToken12.sol b/src/helpers/test-upgrades/IPToken12.sol
new file mode 100644
index 00000000..d87d779d
--- /dev/null
+++ b/src/helpers/test-upgrades/IPToken12.sol
@@ -0,0 +1,126 @@
+// 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 1.2
+ * @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 IPToken12 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_;
+ }
+
+ constructor() {
+ _disableInitializers();
+ }
+
+ 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/helpers/test-upgrades/Tokenizer11.sol b/src/helpers/test-upgrades/Tokenizer12.sol
similarity index 68%
rename from src/helpers/test-upgrades/Tokenizer11.sol
rename to src/helpers/test-upgrades/Tokenizer12.sol
index e613e30e..bd717787 100644
--- a/src/helpers/test-upgrades/Tokenizer11.sol
+++ b/src/helpers/test-upgrades/Tokenizer12.sol
@@ -5,17 +5,20 @@ 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 { IPToken, Metadata as TokenMetadata } from "../../IPToken.sol";
+import { IPToken12 as IPToken, Metadata as TokenMetadata } from "./IPToken12.sol";
import { IPermissioner } from "../../Permissioner.sol";
import { IPNFT } from "../../IPNFT.sol";
+import { IPToken as NewIPtoken } from "../../IPToken.sol";
+
error MustOwnIpnft();
error AlreadyTokenized();
+error ZeroAddress();
-/// @title Tokenizer 1.1
+/// @title Tokenizer 1.2
/// @author molecule.to
-/// @notice tokenizes an IPNFT to an ERC20 token (called IPT) and controls its supply.
-contract Tokenizer11 is UUPSUpgradeable, OwnableUpgradeable {
+/// @notice tokenizes an IPNFT to an ERC20 token (called IPToken or IPT) and controls its supply.
+contract Tokenizer12 is UUPSUpgradeable, OwnableUpgradeable {
event TokensCreated(
uint256 indexed moleculesId,
uint256 indexed ipnftId,
@@ -27,15 +30,23 @@ contract Tokenizer11 is UUPSUpgradeable, OwnableUpgradeable {
string symbol
);
+ event IPTokenImplementationUpdated(IPToken indexed old, IPToken indexed _new);
+ event PermissionerUpdated(IPermissioner indexed old, IPermissioner indexed _new);
+
IPNFT internal ipnft;
- //this is the old term to keep the storage layout intact
+ /// @dev a map of all IPTs. We're staying with the the initial term "synthesized" to keep the storage layout intact
mapping(uint256 => IPToken) public synthesized;
+
+ /// @dev not used, needed to ensure that storage slots are still in order after 1.1 -> 1.2, use ipTokenImplementation
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address immutable tokenImplementation;
/// @dev the permissioner checks if senders have agreed to legal requirements
- IPermissioner permissioner;
+ IPermissioner public permissioner;
+
+ /// @notice the IPToken implementation this Tokenizer spawns
+ IPToken public ipTokenImplementation;
/**
* @param _ipnft the IPNFT contract
@@ -50,12 +61,28 @@ contract Tokenizer11 is UUPSUpgradeable, OwnableUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
- tokenImplementation = address(new IPToken());
+ tokenImplementation = address(0);
_disableInitializers();
}
/**
- * @dev called after an upgrade to reinitialize a new permissioner impl. This is 4 for görli compatibility
+ * @notice sets the new implementation address of the IPToken
+ * @param _ipTokenImplementation address pointing to the new implementation
+ */
+ function setIPTokenImplementation(IPToken _ipTokenImplementation) external onlyOwner {
+ /*
+ could call some functions on old contract to make sure its tokenizer not another contract behind a proxy for safety
+ */
+ if (address(_ipTokenImplementation) == address(0)) {
+ revert ZeroAddress();
+ }
+
+ emit IPTokenImplementationUpdated(ipTokenImplementation, _ipTokenImplementation);
+ ipTokenImplementation = _ipTokenImplementation;
+ }
+
+ /**
+ * @dev called after an upgrade to reinitialize a new permissioner impl.
* @param _permissioner the new TermsPermissioner
*/
function reinit(IPermissioner _permissioner) public onlyOwner reinitializer(4) {
@@ -84,7 +111,7 @@ contract Tokenizer11 is UUPSUpgradeable, OwnableUpgradeable {
}
// https://github.com/OpenZeppelin/workshops/tree/master/02-contracts-clone
- token = IPToken(Clones.clone(tokenImplementation));
+ token = IPToken(Clones.clone(address(ipTokenImplementation)));
string memory name = string.concat("IP Tokens of IPNFT #", Strings.toString(ipnftId));
token.initialize(name, tokenSymbol, TokenMetadata(ipnftId, _msgSender(), agreementCid));
@@ -98,7 +125,7 @@ contract Tokenizer11 is UUPSUpgradeable, OwnableUpgradeable {
//this has been called MoleculesCreated before
emit TokensCreated(tokenHash, ipnftId, address(token), _msgSender(), tokenAmount, agreementCid, name, tokenSymbol);
- permissioner.accept(token, _msgSender(), signedAgreement);
+ permissioner.accept(NewIPtoken(address(token)), _msgSender(), signedAgreement);
token.issue(_msgSender(), tokenAmount);
}
diff --git a/subgraph/.gitignore b/subgraph/.gitignore
index 70a25975..08ba03fa 100644
--- a/subgraph/.gitignore
+++ b/subgraph/.gitignore
@@ -2,5 +2,3 @@ node_modules
build/
generated
-
-subgraph.yaml
diff --git a/subgraph/abis/IPToken.json b/subgraph/abis/IPToken.json
index a3300de1..3927254a 100644
--- a/subgraph/abis/IPToken.json
+++ b/subgraph/abis/IPToken.json
@@ -159,19 +159,6 @@
],
"stateMutability": "nonpayable"
},
- {
- "type": "function",
- "name": "hash",
- "inputs": [],
- "outputs": [
- {
- "name": "",
- "type": "uint256",
- "internalType": "uint256"
- }
- ],
- "stateMutability": "view"
- },
{
"type": "function",
"name": "increaseAllowance",
@@ -200,6 +187,11 @@
"type": "function",
"name": "initialize",
"inputs": [
+ {
+ "name": "ipnftId",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
{
"name": "name",
"type": "string",
@@ -211,26 +203,14 @@
"internalType": "string"
},
{
- "name": "metadata_",
- "type": "tuple",
- "internalType": "struct Metadata",
- "components": [
- {
- "name": "ipnftId",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "originalOwner",
- "type": "address",
- "internalType": "address"
- },
- {
- "name": "agreementCid",
- "type": "string",
- "internalType": "string"
- }
- ]
+ "name": "originalOwner",
+ "type": "address",
+ "internalType": "address"
+ },
+ {
+ "name": "agreementCid",
+ "type": "string",
+ "internalType": "string"
}
],
"outputs": [],
@@ -254,19 +234,6 @@
"outputs": [],
"stateMutability": "nonpayable"
},
- {
- "type": "function",
- "name": "issuer",
- "inputs": [],
- "outputs": [
- {
- "name": "",
- "type": "address",
- "internalType": "address"
- }
- ],
- "stateMutability": "view"
- },
{
"type": "function",
"name": "metadata",
@@ -545,7 +512,7 @@
},
{
"type": "error",
- "name": "OnlyIssuerOrOwner",
+ "name": "MustControlIpnft",
"inputs": []
},
{
diff --git a/subgraph/abis/SharedSalesDistributor.json b/subgraph/abis/SharedSalesDistributor.json
index 82976d74..91cbbdbf 100644
--- a/subgraph/abis/SharedSalesDistributor.json
+++ b/subgraph/abis/SharedSalesDistributor.json
@@ -1,7 +1,7 @@
[
{
"type": "function",
- "name": "afterSale",
+ "name": "UNSAFE_afterSale",
"inputs": [
{
"name": "tokenContract",
@@ -32,7 +32,7 @@
"name": "afterSale",
"inputs": [
{
- "name": "tokenContract",
+ "name": "ipt",
"type": "address",
"internalType": "contract IPToken"
},
@@ -363,6 +363,11 @@
"name": "ListingNotFulfilled",
"inputs": []
},
+ {
+ "type": "error",
+ "name": "MustControlIpnft",
+ "inputs": []
+ },
{
"type": "error",
"name": "NotClaimingYet",
@@ -370,12 +375,12 @@
},
{
"type": "error",
- "name": "OnlyIssuer",
+ "name": "NotSalesBeneficiary",
"inputs": []
},
{
"type": "error",
- "name": "UncappedToken",
+ "name": "OnlySeller",
"inputs": []
}
]
diff --git a/subgraph/abis/Tokenizer.json b/subgraph/abis/Tokenizer.json
index 47b09165..8f5fdb9b 100644
--- a/subgraph/abis/Tokenizer.json
+++ b/subgraph/abis/Tokenizer.json
@@ -4,6 +4,51 @@
"inputs": [],
"stateMutability": "nonpayable"
},
+ {
+ "type": "function",
+ "name": "cap",
+ "inputs": [
+ {
+ "name": "ipToken",
+ "type": "address",
+ "internalType": "contract IPToken"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
+ {
+ "type": "function",
+ "name": "controllerOf",
+ "inputs": [
+ {
+ "name": "ipnftId",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "getIPNFTContract",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "address",
+ "internalType": "contract IPNFT"
+ }
+ ],
+ "stateMutability": "view"
+ },
{
"type": "function",
"name": "initialize",
@@ -35,6 +80,29 @@
],
"stateMutability": "view"
},
+ {
+ "type": "function",
+ "name": "issue",
+ "inputs": [
+ {
+ "name": "ipToken",
+ "type": "address",
+ "internalType": "contract IPToken"
+ },
+ {
+ "name": "amount",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "receiver",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
{
"type": "function",
"name": "owner",
@@ -79,9 +147,9 @@
"name": "reinit",
"inputs": [
{
- "name": "_permissioner",
+ "name": "_ipTokenImplementation",
"type": "address",
- "internalType": "contract IPermissioner"
+ "internalType": "contract IPToken"
}
],
"outputs": [],
@@ -386,7 +454,12 @@
},
{
"type": "error",
- "name": "MustOwnIpnft",
+ "name": "IPTNotControlledByTokenizer",
+ "inputs": []
+ },
+ {
+ "type": "error",
+ "name": "MustControlIpnft",
"inputs": []
},
{
diff --git a/subgraph/makeAbis.sh b/subgraph/makeAbis.sh
index f701bb67..9ad940ae 100755
--- a/subgraph/makeAbis.sh
+++ b/subgraph/makeAbis.sh
@@ -13,7 +13,7 @@ cat ../out/CrowdSale.sol/CrowdSale.json | jq .abi > ./abis/CrowdSale.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
+# add the old Synthesizer's `MoleculesCreated` and `TokensCreated` event to the Tokenizer abi so the subgraph can index them
jq '. += [{
"anonymous": false,
"inputs": [
diff --git a/subgraph/networks.json b/subgraph/networks.json
new file mode 100644
index 00000000..e167cc28
--- /dev/null
+++ b/subgraph/networks.json
@@ -0,0 +1,80 @@
+{
+ "sepolia": {
+ "IPNFT": {
+ "address": "0x152B444e60C526fe4434C721561a077269FcF61a",
+ "startBlock": 5300057
+ },
+ "SchmackoSwap": {
+ "address": "0x9e4c638e703d0Af3a3B9eb488dE79A16d402698f",
+ "startBlock": 5300057
+ },
+ "Tokenizer": {
+ "address": "0xca63411FF5187431028d003eD74B57531408d2F9",
+ "startBlock": 5300776
+ },
+ "CrowdSale": {
+ "address": "0x8cA737E2cdaE1Ceb332bEf7ba9eA711a3a2f8037",
+ "startBlock": 5300777
+ },
+ "StakedLockingCrowdSale": {
+ "address": "0xd1cE2EA7d3b0C9cAB025A4aD762FC00315141ad7",
+ "startBlock": 5300777
+ },
+ "TermsAcceptedPermissioner": {
+ "address": "0xC05D649368d8A5e2E98CAa205d47795de5fCB599",
+ "startBlock": 5300776
+ }
+ },
+ "mainnet": {
+ "IPNFT": {
+ "address": "0xcaD88677CA87a7815728C72D74B4ff4982d54Fc1",
+ "startBlock": 17463429
+ },
+ "SchmackoSwap": {
+ "address": "0xc09b8577c762b5e97a7d640f242e1d9bfaa7eb9d",
+ "startBlock": 17441953
+ },
+ "Tokenizer": {
+ "address": "0x58EB89C69CB389DBef0c130C6296ee271b82f436",
+ "startBlock": 17464363
+ },
+ "CrowdSale": {
+ "address": "0xf0a8d23f38e9cbbe01c4ed37f23bd519b65bc6c2",
+ "startBlock": 18490640
+ },
+ "StakedLockingCrowdSale": {
+ "address": "0x35Bce29F52f51f547998717CD598068Afa2B29B7",
+ "startBlock": 17481804
+ },
+ "TermsAcceptedPermissioner": {
+ "address": "0xC837E02982992B701A1B5e4E21fA01cEB0a628fA",
+ "startBlock": 17790450
+ }
+ },
+ "foundry": {
+ "IPNFT": {
+ "address": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
+ "startBlock": 0
+ },
+ "SchmackoSwap": {
+ "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
+ "startBlock": 0
+ },
+ "Tokenizer": {
+ "address": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
+ "startBlock": 0
+ },
+ "CrowdSale": {
+ "address": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
+ "startBlock": 0
+ },
+ "StakedLockingCrowdSale": {
+ "address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
+ "startBlock": 0
+ },
+ "TermsAcceptedPermissioner": {
+ "address": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
+ "startBlock": 0
+ }
+ }
+}
diff --git a/subgraph/package.json b/subgraph/package.json
index 8d8b7c85..55a2d535 100644
--- a/subgraph/package.json
+++ b/subgraph/package.json
@@ -4,23 +4,21 @@
"scripts": {
"abis": "./makeAbis.sh",
"codegen": "graph codegen",
- "build": "graph codegen && graph build",
- "prepare:local": "mustache config/local.js subgraph.template.yaml > subgraph.yaml",
- "prepare:sepolia": "mustache config/sepolia.js subgraph.template.yaml > subgraph.yaml",
- "prepare:mainnet": "mustache config/mainnet.js subgraph.template.yaml > subgraph.yaml",
- "deploy:sepolia": "env-cmd -x -f ../.env graph deploy ip-nft-sepolia --version-label 1.0.0 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz --deploy-key \\$SATSUMA_DEPLOY_KEY",
+ "build:local": "graph codegen && graph build --network foundry",
+ "build:sepolia": "graph codegen && graph build --network sepolia",
+ "build:mainnet": "graph codegen && graph build --network mainnet",
+ "deploy:local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 moleculeprotocol/ipnft-subgraph",
+ "deploy:sepolia": "env-cmd -x -f ../.env graph deploy ip-nft-sepolia --version-label 1.1.0 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz --deploy-key \\$SATSUMA_DEPLOY_KEY",
"deploy:mainnet": "env-cmd -x -f ../.env graph deploy ip-nft-mainnet --version-label 1.0.0 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz --deploy-key \\$SATSUMA_DEPLOY_KEY",
"create:local": "graph create --node http://localhost:8020/ moleculeprotocol/ipnft-subgraph",
"remove:local": "graph remove --node http://localhost:8020/ moleculeprotocol/ipnft-subgraph",
- "deploy:local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 moleculeprotocol/ipnft-subgraph",
"test": "graph test"
},
"dependencies": {
- "@graphprotocol/graph-cli": "^0.50.1",
- "@graphprotocol/graph-ts": "^0.30.0",
+ "@graphprotocol/graph-cli": "^0.78.0",
+ "@graphprotocol/graph-ts": "^0.35.1",
"dotenv": "^16.0.3",
- "matchstick-as": "0.5.2",
- "mustache": "^4.2.0"
+ "matchstick-as": "0.5.2"
},
"devDependencies": {
"env-cmd": "^10.1.0"
diff --git a/subgraph/src/tokenizerMapping.ts b/subgraph/src/tokenizerMapping.ts
index 91fb5c87..bb8c2223 100644
--- a/subgraph/src/tokenizerMapping.ts
+++ b/subgraph/src/tokenizerMapping.ts
@@ -6,34 +6,20 @@ import { IPToken } from '../generated/templates'
import { IPT } from '../generated/schema'
export function handleIPTsCreated(event: TokensCreatedEvent): void {
- let reacted = new IPT(event.params.tokenContract.toHexString())
+ let ipt = new IPT(event.params.tokenContract.toHexString())
- reacted.createdAt = event.block.timestamp
- reacted.ipnft = event.params.ipnftId.toString()
- reacted.agreementCid = event.params.agreementCid
- reacted.originalOwner = event.params.emitter
- reacted.symbol = event.params.symbol
- reacted.name = event.params.name
- reacted.decimals = BigInt.fromU32(18)
+ ipt.createdAt = event.block.timestamp
+ ipt.ipnft = event.params.ipnftId.toString()
+ ipt.agreementCid = event.params.agreementCid
+ ipt.originalOwner = event.params.emitter
+ ipt.symbol = event.params.symbol
+ ipt.name = event.params.name
+ ipt.decimals = BigInt.fromU32(18)
//these will be updated by the underlying IPT subgraph template
- reacted.totalIssued = BigInt.fromU32(0)
- reacted.circulatingSupply = BigInt.fromU32(0)
+ ipt.totalIssued = BigInt.fromU32(0)
+ ipt.circulatingSupply = BigInt.fromU32(0)
IPToken.create(event.params.tokenContract)
- reacted.save()
+ ipt.save()
}
-
-// export function handleSalesActivated(event: SalesActivatedEvent): void {
-// let reacted = ReactedIpnft.load(event.params.moleculesId.toString());
-// if (!reacted) {
-// log.error('ReactedIpnft not found for id: {}', [
-// event.params.moleculesId.toString()
-// ]);
-// return;
-// }
-// reacted.paymentToken = event.params.paymentToken;
-// reacted.paidPrice = event.params.paidPrice;
-// reacted.claimedShares = BigInt.fromI32(0);
-// reacted.save();
-// }
diff --git a/subgraph/subgraph.template.yaml b/subgraph/subgraph.yaml
similarity index 74%
rename from subgraph/subgraph.template.yaml
rename to subgraph/subgraph.yaml
index 5636c0d8..e0c804c0 100644
--- a/subgraph/subgraph.template.yaml
+++ b/subgraph/subgraph.yaml
@@ -1,17 +1,17 @@
-specVersion: 0.0.4
+specVersion: 1.0.0
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: IPNFT
- network: {{network}}
+ network: foundry
source:
- address: '{{ipnft.address}}'
abi: IPNFT
- startBlock: {{ipnft.startBlock}}
+ address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
+ startBlock: 0
mapping:
kind: ethereum/events
- apiVersion: 0.0.6
+ apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- IPNFT
@@ -30,14 +30,14 @@ dataSources:
file: ./src/ipnftMapping.ts
- kind: ethereum/contract
name: SchmackoSwap
- network: {{network}}
+ network: foundry
source:
- address: '{{schmackoSwap.address}}'
abi: SchmackoSwap
- startBlock: {{schmackoSwap.startBlock}}
+ address: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"
+ startBlock: 0
mapping:
kind: ethereum/events
- apiVersion: 0.0.6
+ apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Listing
@@ -49,21 +49,22 @@ dataSources:
handler: handleListed
- event: Unlisted(uint256,(address,uint256,address,address,uint256,address,uint8))
handler: handleUnlisted
- - event: Purchased(uint256,indexed address,(address,uint256,address,address,uint256,address,uint8))
+ - event: Purchased(uint256,indexed
+ address,(address,uint256,address,address,uint256,address,uint8))
handler: handlePurchased
- event: AllowlistUpdated(uint256,indexed address,bool)
handler: handleAllowlistUpdated
file: ./src/swapMapping.ts
- kind: ethereum/contract
name: Tokenizer
- network: {{network}}
+ network: foundry
source:
- address: '{{tokenizer.address}}'
abi: Tokenizer
- startBlock: {{tokenizer.startBlock}}
+ address: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"
+ startBlock: 0
mapping:
kind: ethereum/events
- apiVersion: 0.0.6
+ apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- IPT
@@ -72,26 +73,23 @@ dataSources:
- name: Tokenizer
file: ./abis/Tokenizer.json
eventHandlers:
- - event: TokensCreated(indexed uint256,indexed uint256,indexed address,address,uint256,string,string,string)
+ - 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)
+ - event: MoleculesCreated(indexed uint256,indexed uint256,indexed
+ address,address,uint256,string,string,string)
handler: handleIPTsCreated
- # - event: SalesActivated(uint256,address,uint256)
- # handler: handleSalesActivated
- # - event: TermsAccepted(indexed uint256,indexed address,bytes)
- # handler: handleTermsAccepted
file: ./src/tokenizerMapping.ts
- kind: ethereum/contract
name: CrowdSale
- network: {{network}}
+ network: foundry
source:
- address: '{{crowdSale.address}}'
abi: CrowdSale
- startBlock: {{crowdSale.startBlock}}
+ address: "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F"
+ startBlock: 0
mapping:
kind: ethereum/events
- apiVersion: 0.0.6
+ apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- CrowdSale
@@ -103,7 +101,8 @@ dataSources:
- name: IERC20Metadata
file: ./abis/IERC20Metadata.json
eventHandlers:
- - event: Started(indexed uint256,indexed address,(address,address,address,uint256,uint256,uint64,address),uint16)
+ - event: Started(indexed uint256,indexed
+ address,(address,address,address,uint256,uint256,uint64,address),uint16)
handler: handleStarted
- event: Settled(indexed uint256,uint256,uint256)
handler: handleSettled
@@ -120,14 +119,14 @@ dataSources:
file: ./src/crowdSaleMapping.ts
- kind: ethereum/contract
name: StakedLockingCrowdSale
- network: {{network}}
+ network: foundry
source:
- address: '{{stakedLockingCrowdSale.address}}'
abi: StakedLockingCrowdSale
- startBlock: {{stakedLockingCrowdSale.startBlock}}
+ address: "0x0B306BF915C4d645ff596e518fAf3F9669b97016"
+ startBlock: 0
mapping:
kind: ethereum/events
- apiVersion: 0.0.6
+ apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- CrowdSale
@@ -139,10 +138,11 @@ dataSources:
- name: IERC20Metadata
file: ./abis/IERC20Metadata.json
eventHandlers:
- # the initial crowdsale contract didn't use any fees
- - event: Started(indexed uint256,indexed address,(address,address,address,uint256,uint256,uint64,address),(address,address,uint256),address,uint256,uint256)
+ - event: Started(indexed uint256,indexed
+ address,(address,address,address,uint256,uint256,uint64,address),(address,address,uint256),address,uint256,uint256)
handler: handleStartedLegacy
- - event: Started(indexed uint256,indexed address,(address,address,address,uint256,uint256,uint64,address),(address,address,uint256),address,uint256,uint256,uint16)
+ - event: Started(indexed uint256,indexed
+ address,(address,address,address,uint256,uint256,uint64,address),(address,address,uint256),address,uint256,uint256,uint16)
handler: handleStarted
- event: Settled(indexed uint256,uint256,uint256)
handler: handleSettled
@@ -165,14 +165,14 @@ dataSources:
file: ./src/stakedLockingCrowdSaleMapping.ts
- kind: ethereum/contract
name: TermsAcceptedPermissioner
- network: {{network}}
+ network: foundry
source:
- address: '{{termsAcceptedPermissioner.address}}'
abi: TermsAcceptedPermissioner
- startBlock: {{termsAcceptedPermissioner.startBlock}}
+ address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318"
+ startBlock: 0
mapping:
kind: ethereum/events
- apiVersion: 0.0.6
+ apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- IPTBalance
@@ -183,16 +183,15 @@ dataSources:
- event: TermsAccepted(indexed address,indexed address,bytes)
handler: handleTermsAccepted
file: ./src/termsAcceptedPermissionerMapping.ts
-
templates:
- name: IPToken
kind: ethereum/contract
- network: {{network}}
+ network: foundry
source:
abi: IPToken
mapping:
kind: ethereum/events
- apiVersion: 0.0.6
+ apiVersion: 0.0.7
language: wasm/assemblyscript
file: ./src/iptMapping.ts
entities:
@@ -205,16 +204,14 @@ templates:
handler: handleTransfer
- event: Capped(uint256)
handler: handleCapped
- # - event: SharesClaimed(indexed uint256,indexed address,uint256)
- # handler: handleSharesClaimed
- name: TimelockedToken
kind: ethereum/contract
- network: {{network}}
+ network: foundry
source:
abi: TimelockedToken
mapping:
kind: ethereum/events
- apiVersion: 0.0.6
+ apiVersion: 0.0.7
language: wasm/assemblyscript
file: ./src/timelockedTokenMapping.ts
entities:
@@ -224,7 +221,8 @@ templates:
- name: TimelockedToken
file: ./abis/TimelockedToken.json
eventHandlers:
- - event: ScheduleCreated(indexed bytes32,indexed address,indexed address,uint256,uint64)
+ - event: ScheduleCreated(indexed bytes32,indexed address,indexed
+ address,uint256,uint64)
handler: handleScheduled
- event: ScheduleReleased(indexed bytes32,indexed address,uint256)
handler: handleReleased
diff --git a/subgraph/yarn.lock b/subgraph/yarn.lock
index 4d264c0e..deeb563a 100644
--- a/subgraph/yarn.lock
+++ b/subgraph/yarn.lock
@@ -217,13 +217,15 @@
graphql-import-node "^0.0.5"
js-yaml "^4.1.0"
-"@graphprotocol/graph-cli@^0.50.1":
- version "0.50.1"
- resolved "https://registry.yarnpkg.com/@graphprotocol/graph-cli/-/graph-cli-0.50.1.tgz#ca44138761e4d111e27c3645dd6bb6ea6e07753e"
- integrity sha512-tk3e5NYBwXRuRD1y5+UKTRDv+Hwf3a78qmcpGOpIMjMgIpvDnepApRbqMqxt3Ma/RCRkACp0Kmkt3O5Ey4xlkQ==
+"@graphprotocol/graph-cli@^0.78.0":
+ version "0.78.0"
+ resolved "https://registry.yarnpkg.com/@graphprotocol/graph-cli/-/graph-cli-0.78.0.tgz#9e64bdeef4be66793f92d40db3cfafac2c42cf2e"
+ integrity sha512-Zw/6k7YYjVoHFgy1BbqpllPUSjzk1ubkIBbYp7HlD//mPUnUhWQLgId92yENw1DqbWOx1R+LhXjeKkwbJgjUxA==
dependencies:
"@float-capital/float-subgraph-uncrashable" "^0.0.0-alpha.4"
- "@oclif/core" "2.8.4"
+ "@oclif/core" "2.8.6"
+ "@oclif/plugin-autocomplete" "^2.3.6"
+ "@oclif/plugin-not-found" "^2.4.0"
"@whatwg-node/fetch" "^0.8.4"
assemblyscript "0.19.23"
binary-install-raw "0.0.13"
@@ -234,14 +236,14 @@
dockerode "2.5.8"
fs-extra "9.1.0"
glob "9.3.5"
- gluegun "5.1.2"
+ gluegun "5.1.6"
graphql "15.5.0"
immutable "4.2.1"
ipfs-http-client "55.0.0"
jayson "4.0.0"
js-yaml "3.14.1"
- prettier "1.19.1"
- request "2.88.2"
+ open "8.4.2"
+ prettier "3.0.3"
semver "7.4.0"
sync-request "6.1.0"
tmp-promise "3.0.3"
@@ -249,10 +251,10 @@
which "2.0.2"
yaml "1.10.2"
-"@graphprotocol/graph-ts@^0.30.0":
- version "0.30.0"
- resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.30.0.tgz#591dee3c7d9fc236ad57ce0712779e94aef9a50a"
- integrity sha512-h5tJqlsZXglGYM0PcBsBOqof4PT0Fr4Z3QBTYN/IjMF3VvRX2A8/bdpqaAnva+2N0uAfXXwRcwcOcW5O35yzXw==
+"@graphprotocol/graph-ts@^0.35.1":
+ version "0.35.1"
+ resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.35.1.tgz#1e1ecc36d8f7a727ef3a6f1fed4c5ce16de378c2"
+ integrity sha512-74CfuQmf7JI76/XCC34FTkMMKeaf+3Pn0FIV3m9KNeaOJ+OI3CvjMIVRhOZdKcJxsFCBGaCCl0eQjh47xTjxKA==
dependencies:
assemblyscript "0.19.10"
@@ -318,10 +320,10 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
-"@oclif/core@2.8.4":
- version "2.8.4"
- resolved "https://registry.yarnpkg.com/@oclif/core/-/core-2.8.4.tgz#7b453be6d4cd060ff4990bc8e31824a1de308354"
- integrity sha512-VlFDhoAJ1RDwcpDF46wAlciWTIryapMUViACttY9GwX6Ci6Lud1awe/pC3k4jad5472XshnPQV4bHAl4a/yxpA==
+"@oclif/core@2.8.6":
+ version "2.8.6"
+ resolved "https://registry.yarnpkg.com/@oclif/core/-/core-2.8.6.tgz#7eb6984108f471ad0d719d3c07cde14c47ab17c5"
+ integrity sha512-1QlPaHMhOORySCXkQyzjsIsy2GYTilOw3LkjeHkCgsPJQjAT4IclVytJusWktPbYNys9O+O4V23J44yomQvnBQ==
dependencies:
"@types/cli-progress" "^3.11.0"
ansi-escapes "^4.3.2"
@@ -353,6 +355,58 @@
wordwrap "^1.0.0"
wrap-ansi "^7.0.0"
+"@oclif/core@^2.15.0":
+ version "2.16.0"
+ resolved "https://registry.yarnpkg.com/@oclif/core/-/core-2.16.0.tgz#e6f3c6c359d4313a15403d8652bbdd0e99ce4b3a"
+ integrity sha512-dL6atBH0zCZl1A1IXCKJgLPrM/wR7K+Wi401E/IvqsK8m2iCHW+0TEOGrans/cuN3oTW+uxIyJFHJ8Im0k4qBw==
+ dependencies:
+ "@types/cli-progress" "^3.11.0"
+ ansi-escapes "^4.3.2"
+ ansi-styles "^4.3.0"
+ cardinal "^2.1.1"
+ chalk "^4.1.2"
+ clean-stack "^3.0.1"
+ cli-progress "^3.12.0"
+ debug "^4.3.4"
+ ejs "^3.1.8"
+ get-package-type "^0.1.0"
+ globby "^11.1.0"
+ hyperlinker "^1.0.0"
+ indent-string "^4.0.0"
+ is-wsl "^2.2.0"
+ js-yaml "^3.14.1"
+ natural-orderby "^2.0.3"
+ object-treeify "^1.1.33"
+ password-prompt "^1.1.2"
+ slice-ansi "^4.0.0"
+ string-width "^4.2.3"
+ strip-ansi "^6.0.1"
+ supports-color "^8.1.1"
+ supports-hyperlinks "^2.2.0"
+ ts-node "^10.9.1"
+ tslib "^2.5.0"
+ widest-line "^3.1.0"
+ wordwrap "^1.0.0"
+ wrap-ansi "^7.0.0"
+
+"@oclif/plugin-autocomplete@^2.3.6":
+ version "2.3.10"
+ resolved "https://registry.yarnpkg.com/@oclif/plugin-autocomplete/-/plugin-autocomplete-2.3.10.tgz#787f6208cdfe10ffc68ad89e9e7f1a7ad0e8987f"
+ integrity sha512-Ow1AR8WtjzlyCtiWWPgzMyT8SbcDJFr47009riLioHa+MHX2BCDtVn2DVnN/E6b9JlPV5ptQpjefoRSNWBesmg==
+ dependencies:
+ "@oclif/core" "^2.15.0"
+ chalk "^4.1.0"
+ debug "^4.3.4"
+
+"@oclif/plugin-not-found@^2.4.0":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@oclif/plugin-not-found/-/plugin-not-found-2.4.3.tgz#3d24095adb0f3876cb4bcfdfdcb775086cf6d4b5"
+ integrity sha512-nIyaR4y692frwh7wIHZ3fb+2L6XEecQwRDIb4zbEam0TvaVmBQWZoColQyWA84ljFBPZ8XWiQyTz+ixSwdRkqg==
+ dependencies:
+ "@oclif/core" "^2.15.0"
+ chalk "^4"
+ fast-levenshtein "^3.0.0"
+
"@peculiar/asn1-schema@^2.3.6":
version "2.3.6"
resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.6.tgz#3dd3c2ade7f702a9a94dfb395c192f5fa5d6b922"
@@ -619,16 +673,6 @@ acorn@^8.4.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
-ajv@^6.12.3:
- version "6.12.6"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
- integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
- dependencies:
- fast-deep-equal "^3.1.1"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.4.1"
- uri-js "^4.2.2"
-
ansi-colors@^4.1.1:
version "4.1.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
@@ -735,13 +779,6 @@ asap@~2.0.6:
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
-asn1@~0.2.3:
- version "0.2.6"
- resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
- integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
- dependencies:
- safer-buffer "~2.1.0"
-
asn1js@^3.0.1, asn1js@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38"
@@ -768,10 +805,10 @@ assemblyscript@0.19.23:
long "^5.2.0"
source-map-support "^0.5.20"
-assert-plus@1.0.0, assert-plus@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
- integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==
+astral-regex@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
+ integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
async@^3.2.3:
version "3.2.4"
@@ -788,16 +825,6 @@ at-least-node@^1.0.0:
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
-aws-sign2@~0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
- integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==
-
-aws4@^1.8.0:
- version "1.12.0"
- resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3"
- integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==
-
axios@^0.21.1, axios@^0.21.4:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
@@ -822,13 +849,6 @@ base64-js@^1.3.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-bcrypt-pbkdf@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
- integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==
- dependencies:
- tweetnacl "^0.14.3"
-
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
@@ -1039,7 +1059,7 @@ chalk@^2.0.0, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
-chalk@^4.0.2, chalk@^4.1.2:
+chalk@^4, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -1150,7 +1170,7 @@ colors@1.4.0, colors@^1.1.2:
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
-combined-stream@^1.0.6, combined-stream@~1.0.6:
+combined-stream@^1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@@ -1182,11 +1202,6 @@ concat-stream@^1.6.0, concat-stream@^1.6.2, concat-stream@~1.6.2:
readable-stream "^2.2.2"
typedarray "^0.0.6"
-core-util-is@1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
- integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
-
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
@@ -1251,13 +1266,6 @@ cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"
-dashdash@^1.12.0:
- version "1.14.1"
- resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
- integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==
- dependencies:
- assert-plus "^1.0.0"
-
debug@4.3.4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
@@ -1279,6 +1287,11 @@ defaults@^1.0.3:
dependencies:
clone "^1.0.2"
+define-lazy-prop@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
+ integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
+
delay@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d"
@@ -1341,20 +1354,12 @@ dotenv@^16.0.3:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07"
integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
-ecc-jsbn@~0.1.1:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
- integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==
- dependencies:
- jsbn "~0.1.0"
- safer-buffer "^2.1.0"
-
-ejs@3.1.6:
- version "3.1.6"
- resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a"
- integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==
+ejs@3.1.8:
+ version "3.1.8"
+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b"
+ integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==
dependencies:
- jake "^10.6.1"
+ jake "^10.8.5"
ejs@^3.1.8:
version "3.1.9"
@@ -1531,21 +1536,6 @@ execa@5.1.1:
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
-extend@~3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
- integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
-
-extsprintf@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
- integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==
-
-extsprintf@^1.2.0:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
- integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
-
eyes@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
@@ -1556,11 +1546,6 @@ fast-decode-uri-component@^1.0.1:
resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543"
integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==
-fast-deep-equal@^3.1.1:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
- integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
-
fast-fifo@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.2.0.tgz#2ee038da2468e8623066dee96958b0c1763aa55a"
@@ -1577,10 +1562,12 @@ fast-glob@^3.2.9:
merge2 "^1.3.0"
micromatch "^4.0.4"
-fast-json-stable-stringify@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
- integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+fast-levenshtein@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz#37b899ae47e1090e40e3fd2318e4d5f0142ca912"
+ integrity sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==
+ dependencies:
+ fastest-levenshtein "^1.0.7"
fast-querystring@^1.1.1:
version "1.1.1"
@@ -1596,6 +1583,11 @@ fast-url-parser@^1.1.3:
dependencies:
punycode "^1.3.2"
+fastest-levenshtein@^1.0.7:
+ version "1.0.16"
+ resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
+ integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
+
fastq@^1.6.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a"
@@ -1603,7 +1595,7 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
-filelist@^1.0.1, filelist@^1.0.4:
+filelist@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==
@@ -1622,11 +1614,6 @@ follow-redirects@^1.14.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
-forever-agent@~0.6.1:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
- integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==
-
form-data@^2.2.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
@@ -1636,15 +1623,6 @@ form-data@^2.2.0:
combined-stream "^1.0.6"
mime-types "^2.1.12"
-form-data@~2.3.2:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
- integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.6"
- mime-types "^2.1.12"
-
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
@@ -1719,13 +1697,6 @@ get-stream@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
-getpass@^0.1.1:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
- integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==
- dependencies:
- assert-plus "^1.0.0"
-
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -1767,10 +1738,10 @@ globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
-gluegun@5.1.2:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/gluegun/-/gluegun-5.1.2.tgz#ffa0beda0fb6bbc089a867157b08602beae2c8cf"
- integrity sha512-Cwx/8S8Z4YQg07a6AFsaGnnnmd8mN17414NcPS3OoDtZRwxgsvwRNJNg69niD6fDa8oNwslCG0xH7rEpRNNE/g==
+gluegun@5.1.6:
+ version "5.1.6"
+ resolved "https://registry.yarnpkg.com/gluegun/-/gluegun-5.1.6.tgz#74ec13193913dc610f5c1a4039972c70c96a7bad"
+ integrity sha512-9zbi4EQWIVvSOftJWquWzr9gLX2kaDgPkNR5dYWbM53eVvCI3iKuxLlnKoHC0v4uPoq+Kr/+F569tjoFbA4DSA==
dependencies:
apisauce "^2.1.5"
app-module-path "^2.2.0"
@@ -1778,7 +1749,7 @@ gluegun@5.1.2:
colors "1.4.0"
cosmiconfig "7.0.1"
cross-spawn "7.0.3"
- ejs "3.1.6"
+ ejs "3.1.8"
enquirer "2.3.6"
execa "5.1.1"
fs-jetpack "4.3.1"
@@ -1823,19 +1794,6 @@ graphql@^16.6.0:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb"
integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==
-har-schema@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
- integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==
-
-har-validator@~5.1.3:
- version "5.1.5"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
- integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
- dependencies:
- ajv "^6.12.3"
- har-schema "^2.0.0"
-
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -1901,15 +1859,6 @@ http-response-object@^3.0.1:
dependencies:
"@types/node" "^10.0.3"
-http-signature@~1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
- integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==
- dependencies:
- assert-plus "^1.0.0"
- jsprim "^1.2.2"
- sshpk "^1.7.0"
-
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
@@ -2089,7 +2038,7 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
-is-docker@^2.0.0:
+is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
@@ -2148,11 +2097,6 @@ is-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
-is-typedarray@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
- integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
-
is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
@@ -2185,11 +2129,6 @@ isomorphic-ws@^4.0.1:
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==
-isstream@~0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
- integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
-
it-all@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/it-all/-/it-all-1.0.6.tgz#852557355367606295c4c3b7eff0136f07749335"
@@ -2235,16 +2174,6 @@ it-to-stream@^1.0.0:
p-fifo "^1.0.0"
readable-stream "^3.6.0"
-jake@^10.6.1:
- version "10.8.6"
- resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.6.tgz#227a96786a1e035214e0ba84b482d6223d41ef04"
- integrity sha512-G43Ub9IYEFfu72sua6rzooi8V8Gz2lkfk48rW20vEWCGizeaEPlKB1Kh8JIA84yQbiAEfqlPmSpGgCKKxH3rDA==
- dependencies:
- async "^3.2.3"
- chalk "^4.0.2"
- filelist "^1.0.4"
- minimatch "^3.1.2"
-
jake@^10.8.5:
version "10.8.5"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
@@ -2298,27 +2227,12 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
-jsbn@~0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
- integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
-
json-parse-even-better-errors@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
-json-schema-traverse@^0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
- integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
-
-json-schema@0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
- integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
-
-json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
+json-stringify-safe@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
@@ -2337,16 +2251,6 @@ jsonparse@^1.2.0:
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
-jsprim@^1.2.2:
- version "1.4.2"
- resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
- integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==
- dependencies:
- assert-plus "1.0.0"
- extsprintf "1.3.0"
- json-schema "0.4.0"
- verror "1.10.0"
-
keccak@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.3.tgz#4bc35ad917be1ef54ff246f904c2bbbf9ac61276"
@@ -2516,7 +2420,7 @@ mime-db@1.52.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
-mime-types@^2.1.12, mime-types@~2.1.19:
+mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -2538,7 +2442,7 @@ minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
-minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
+minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -2642,11 +2546,6 @@ multiformats@^9.4.13, multiformats@^9.4.2, multiformats@^9.4.5, multiformats@^9.
resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37"
integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==
-mustache@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
- integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
-
nanoid@^3.0.2, nanoid@^3.1.20, nanoid@^3.1.23:
version "3.3.6"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
@@ -2709,11 +2608,6 @@ number-to-bn@1.7.0:
bn.js "4.11.6"
strip-hex-prefix "1.0.0"
-oauth-sign@~0.9.0:
- version "0.9.0"
- resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
- integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-
object-assign@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -2743,6 +2637,15 @@ onetime@^5.1.0, onetime@^5.1.2:
dependencies:
mimic-fn "^2.1.0"
+open@8.4.2:
+ version "8.4.2"
+ resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
+ integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
+ dependencies:
+ define-lazy-prop "^2.0.0"
+ is-docker "^2.1.1"
+ is-wsl "^2.2.0"
+
ora@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/ora/-/ora-4.0.2.tgz#0e1e68fd45b135d28648b27cf08081fa6e8a297d"
@@ -2843,11 +2746,6 @@ pbkdf2@^3.0.17:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
-performance-now@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
- integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
-
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
@@ -2858,10 +2756,10 @@ pluralize@^8.0.0:
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
-prettier@1.19.1:
- version "1.19.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
- integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
+prettier@3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643"
+ integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==
process-nextick-args@~2.0.0:
version "2.0.1"
@@ -2894,11 +2792,6 @@ protobufjs@^6.10.2:
"@types/node" ">=13.7.0"
long "^4.0.0"
-psl@^1.1.28:
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
- integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
-
pump@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
@@ -2912,11 +2805,6 @@ punycode@^1.3.2:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
-punycode@^2.1.0, punycode@^2.1.1:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
- integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
-
pvtsutils@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.2.tgz#9f8570d132cdd3c27ab7d51a2799239bf8d8d5de"
@@ -2936,11 +2824,6 @@ qs@^6.4.0:
dependencies:
side-channel "^1.0.4"
-qs@~6.5.2:
- version "6.5.3"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
- integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
-
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -3013,32 +2896,6 @@ redeyed@~2.1.0:
dependencies:
esprima "~4.0.0"
-request@2.88.2:
- version "2.88.2"
- resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
- integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
- dependencies:
- aws-sign2 "~0.7.0"
- aws4 "^1.8.0"
- caseless "~0.12.0"
- combined-stream "~1.0.6"
- extend "~3.0.2"
- forever-agent "~0.6.1"
- form-data "~2.3.2"
- har-validator "~5.1.3"
- http-signature "~1.2.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.19"
- oauth-sign "~0.9.0"
- performance-now "^2.1.0"
- qs "~6.5.2"
- safe-buffer "^5.1.2"
- tough-cookie "~2.5.0"
- tunnel-agent "^0.6.0"
- uuid "^3.3.2"
-
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -3108,7 +2965,7 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
+"safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -3209,6 +3066,15 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+slice-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
+ integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
+ dependencies:
+ ansi-styles "^4.0.0"
+ astral-regex "^2.0.0"
+ is-fullwidth-code-point "^3.0.0"
+
source-map-support@^0.5.20:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
@@ -3232,21 +3098,6 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
-sshpk@^1.7.0:
- version "1.17.0"
- resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5"
- integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==
- dependencies:
- asn1 "~0.2.3"
- assert-plus "^1.0.0"
- bcrypt-pbkdf "^1.0.0"
- dashdash "^1.12.0"
- ecc-jsbn "~0.1.1"
- getpass "^0.1.1"
- jsbn "~0.1.0"
- safer-buffer "^2.0.2"
- tweetnacl "~0.14.0"
-
stream-to-it@^0.2.2:
version "0.2.4"
resolved "https://registry.yarnpkg.com/stream-to-it/-/stream-to-it-0.2.4.tgz#d2fd7bfbd4a899b4c0d6a7e6a533723af5749bd0"
@@ -3450,14 +3301,6 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
-tough-cookie@~2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
- integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
- dependencies:
- psl "^1.1.28"
- punycode "^2.1.1"
-
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
@@ -3487,18 +3330,6 @@ tslib@^2.0.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338"
integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==
-tunnel-agent@^0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
- integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
- dependencies:
- safe-buffer "^5.0.1"
-
-tweetnacl@^0.14.3, tweetnacl@~0.14.0:
- version "0.14.5"
- resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
- integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==
-
type-fest@^0.21.3:
version "0.21.3"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
@@ -3521,13 +3352,6 @@ universalify@^2.0.0:
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
-uri-js@^4.2.2:
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
- integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
- dependencies:
- punycode "^2.1.0"
-
urlpattern-polyfill@^8.0.0:
version "8.0.2"
resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz#99f096e35eff8bf4b5a2aa7d58a1523d6ebc7ce5"
@@ -3543,11 +3367,6 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
-uuid@^3.3.2:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
- integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
-
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
@@ -3563,15 +3382,6 @@ varint@^6.0.0:
resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0"
integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==
-verror@1.10.0:
- version "1.10.0"
- resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
- integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==
- dependencies:
- assert-plus "^1.0.0"
- core-util-is "1.0.2"
- extsprintf "^1.2.0"
-
wabt@1.0.24:
version "1.0.24"
resolved "https://registry.yarnpkg.com/wabt/-/wabt-1.0.24.tgz#c02e0b5b4503b94feaf4a30a426ef01c1bea7c6c"
diff --git a/test/CrowdSalePermissioned.t.sol b/test/CrowdSalePermissioned.t.sol
index 4a6959cf..86fb14b0 100644
--- a/test/CrowdSalePermissioned.t.sol
+++ b/test/CrowdSalePermissioned.t.sol
@@ -13,7 +13,7 @@ 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, BlindPermissioner } from "../src/Permissioner.sol";
-import { MustOwnIpnft, AlreadyTokenized, Tokenizer, ZeroAddress } from "../src/Tokenizer.sol";
+import { MustControlIpnft, AlreadyTokenized, Tokenizer, ZeroAddress } from "../src/Tokenizer.sol";
import { IPNFT } from "../src/IPNFT.sol";
import { TokenVesting } from "@moleculeprotocol/token-vesting/TokenVesting.sol";
import { TimelockedToken } from "../src/TimelockedToken.sol";
@@ -52,8 +52,15 @@ contract CrowdSalePermissionedTest is Test {
ipnft.initialize();
ipnft.setAuthorizer(new AcceptAllAuthorizer());
- Tokenizer tokenizer = Tokenizer(address(new ERC1967Proxy(address(new Tokenizer()), "")));
- tokenizer.initialize(ipnft, new BlindPermissioner());
+ Tokenizer tokenizer = Tokenizer(
+ address(
+ new ERC1967Proxy(
+ address(new Tokenizer()),
+ abi.encodeWithSelector(Tokenizer.initialize.selector, [address(ipnft), address(new BlindPermissioner())])
+ )
+ )
+ );
+
tokenizer.setIPTokenImplementation(new IPToken());
biddingToken = new FakeERC20("USD token", "USDC");
@@ -61,11 +68,7 @@ contract CrowdSalePermissionedTest is Test {
crowdSale = new StakedLockingCrowdSale();
- vestedDao = new TokenVesting(
- daoToken,
- string(abi.encodePacked("Vested ", daoToken.name())),
- string(abi.encodePacked("v", daoToken.symbol()))
- );
+ vestedDao = new TokenVesting(daoToken, string(abi.encodePacked("Vested ", daoToken.name())), string(abi.encodePacked("v", daoToken.symbol())));
vestedDao.grantRole(vestedDao.ROLE_CREATE_SCHEDULE(), address(crowdSale));
crowdSale.trustVestingContract(vestedDao);
vm.stopPrank();
@@ -77,12 +80,14 @@ contract CrowdSalePermissionedTest is Test {
auctionToken = tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, "");
auctionToken.issue(emitter, 500_000 ether);
+
vm.stopPrank();
- vm.startPrank(deployer);
+ // here's a funny hack we're utilizing only for this test:
+ // to make the tokenization easier, we're using a BlindPermissioner above
+ // from now on, we're switching to a TermsAcceptedPermissioner
permissioner = new TermsAcceptedPermissioner();
- tokenizer.reinit(permissioner);
- vm.stopPrank();
+ vm.store(address(tokenizer), bytes32(uint256(3)), bytes32(uint256(uint160(address(permissioner)))));
vm.startPrank(bidder);
biddingToken.mint(bidder, 1_000_000 ether);
diff --git a/test/Forking/Tokenizer13UpgradeForkTest.t.sol b/test/Forking/Tokenizer13UpgradeForkTest.t.sol
new file mode 100644
index 00000000..15fccfa4
--- /dev/null
+++ b/test/Forking/Tokenizer13UpgradeForkTest.t.sol
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.18;
+
+import "forge-std/Test.sol";
+import { console } from "forge-std/console.sol";
+
+import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
+
+import { IPNFT } from "../../src/IPNFT.sol";
+
+import { MustControlIpnft, AlreadyTokenized, Tokenizer } from "../../src/Tokenizer.sol";
+import { Tokenizer12 } from "../../src/helpers/test-upgrades/Tokenizer12.sol";
+import { IPToken12, OnlyIssuerOrOwner } from "../../src/helpers/test-upgrades/IPToken12.sol";
+import { IPToken, TokenCapped, Metadata } from "../../src/IPToken.sol";
+import { IPermissioner, BlindPermissioner } from "../../src/Permissioner.sol";
+
+contract Tokenizer13UpgradeForkTest is Test {
+ using SafeERC20Upgradeable for IPToken;
+
+ uint256 mainnetFork;
+
+ string ipfsUri = "ipfs://bafkreiankqd3jvpzso6khstnaoxovtyezyatxdy7t2qzjoolqhltmasqki";
+ string agreementCid = "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq";
+ uint256 MINTING_FEE = 0.001 ether;
+ string DEFAULT_SYMBOL = "IPT-0001";
+
+ address mainnetDeployer = 0x34021576F01275A429163a56908Bd02b43e2B7e1;
+ address mainnetOwner = 0xCfA0F84660fB33bFd07C369E5491Ab02C449f71B;
+ address mainnetTokenizer = 0x58EB89C69CB389DBef0c130C6296ee271b82f436;
+ address mainnetIPNFT = 0xcaD88677CA87a7815728C72D74B4ff4982d54Fc1;
+
+ address vitaDaoTreasury = 0xF5307a74d1550739ef81c6488DC5C7a6a53e5Ac2;
+
+ // paulhaas.eth
+ address paulhaas = 0x45602BFBA960277bF917C1b2007D1f03d7bd29e4;
+
+ IPNFT ipnft = IPNFT(mainnetIPNFT);
+ Tokenizer tokenizer13;
+ IPToken newIPTokenImplementation;
+
+ address alice = makeAddr("alice");
+
+ function setUp() public {
+ mainnetFork = vm.createFork(vm.envString("MAINNET_RPC_URL"), 20240430);
+ vm.selectFork(mainnetFork);
+ }
+
+ function upgradeToTokenizer13() public {
+ vm.startPrank(mainnetDeployer);
+ Tokenizer newTokenizerImplementation = new Tokenizer();
+ newIPTokenImplementation = new IPToken();
+ vm.stopPrank();
+
+ vm.startPrank(mainnetOwner);
+ Tokenizer12 tokenizer12 = Tokenizer12(mainnetTokenizer);
+ //todo: make sure that the legacy IPTs are indexed now
+ bytes memory upgradeCallData = abi.encodeWithSelector(Tokenizer.reinit.selector, address(newIPTokenImplementation));
+ tokenizer12.upgradeToAndCall(address(newTokenizerImplementation), upgradeCallData);
+ tokenizer13 = Tokenizer(mainnetTokenizer);
+ }
+
+ function testCanUpgradeToV13() public {
+ upgradeToTokenizer13();
+ assertEq(address(tokenizer13.ipTokenImplementation()), address(newIPTokenImplementation));
+ assertEq(address(tokenizer13.permissioner()), 0xC837E02982992B701A1B5e4E21fA01cEB0a628fA);
+
+ vm.startPrank(alice);
+ vm.expectRevert("Initializable: contract is already initialized");
+ tokenizer13.initialize(IPNFT(address(0)), BlindPermissioner(address(0)));
+
+ vm.expectRevert("Initializable: contract is already initialized");
+ newIPTokenImplementation.initialize(2, "Foo", "Bar", alice, "abcde");
+
+ vm.startPrank(mainnetOwner);
+ vm.expectRevert("Initializable: contract is already initialized");
+ tokenizer13.initialize(IPNFT(address(0)), BlindPermissioner(address(0)));
+
+ vm.expectRevert("Initializable: contract is already initialized");
+ tokenizer13.reinit(newIPTokenImplementation);
+ }
+
+ function testOldIPTsAreMigratedAndCantBeReminted() public {
+ upgradeToTokenizer13();
+
+ assertEq(address(tokenizer13.synthesized(2)), 0x6034e0d6999741f07cb6Fb1162cBAA46a1D33d36);
+ assertEq(address(tokenizer13.synthesized(28)), 0x7b66E84Be78772a3afAF5ba8c1993a1B5D05F9C2);
+ assertEq(address(tokenizer13.synthesized(37)), 0xBcE56276591128047313e64744b3EBE03998783f);
+ assertEq(address(tokenizer13.synthesized(31415269)), address(0));
+
+ deployCodeTo("Permissioner.sol:BlindPermissioner", "", address(tokenizer13.permissioner()));
+
+ address vitaFASTMultisig = 0xf7990CD398daFB4fe5Fd6B9228B8e6f72b296555;
+
+ vm.startPrank(vitaFASTMultisig);
+ vm.expectRevert(AlreadyTokenized.selector);
+ tokenizer13.tokenizeIpnft(2, 1_000_000 ether, "VITA-FAST", "bafkreig274nfj7srmtnb5wd5wlwm3ig2s63wovlz7i3noodjlfz2tm3n5q", bytes(""));
+
+ vm.startPrank(alice);
+ vm.expectRevert(MustControlIpnft.selector);
+ tokenizer13.tokenizeIpnft(2, 1_000_000 ether, "VITA-FAST", "bafkreig274nfj7srmtnb5wd5wlwm3ig2s63wovlz7i3noodjlfz2tm3n5q", bytes(""));
+ }
+
+ function testTokenizeNewIPTs() public {
+ upgradeToTokenizer13();
+ address valleyDaoMultisig = 0xD920E60b798A2F5a8332799d8a23075c9E77d5F8;
+ uint256 valleyDaoIpnftId = 3; //hasnt been tokenized yet
+ deployCodeTo("Permissioner.sol:BlindPermissioner", "", address(tokenizer13.permissioner()));
+
+ assertEq(ipnft.ownerOf(valleyDaoIpnftId), valleyDaoMultisig);
+
+ vm.startPrank(valleyDaoMultisig);
+ IPToken ipt = tokenizer13.tokenizeIpnft(valleyDaoIpnftId, 1_000_000 ether, "VALLEY", agreementCid, "");
+ assertEq(ipt.balanceOf(valleyDaoMultisig), 1_000_000 ether);
+ ipt.transfer(alice, 100_000 ether);
+ assertEq(ipt.balanceOf(valleyDaoMultisig), 900_000 ether);
+ assertEq(ipt.balanceOf(alice), 100_000 ether);
+
+ //controlling the IPT from its own interface
+ ipt.issue(valleyDaoMultisig, 1_000_000 ether);
+ assertEq(ipt.totalSupply(), 2_000_000 ether);
+ assertEq(ipt.balanceOf(valleyDaoMultisig), 1_900_000 ether);
+
+ ipt.cap();
+
+ vm.expectRevert(TokenCapped.selector);
+ ipt.issue(valleyDaoMultisig, 100);
+
+ vm.stopPrank();
+ }
+
+ function testOldTokensAreStillControllable() public {
+ upgradeToTokenizer13();
+ address vitaFASTAddress = 0x6034e0d6999741f07cb6Fb1162cBAA46a1D33d36;
+ address vitaFASTMultisig = 0xf7990CD398daFB4fe5Fd6B9228B8e6f72b296555;
+
+ IPToken vitaFast = IPToken(vitaFASTAddress);
+
+ assertEq(vitaFast.balanceOf(paulhaas), 16942857059768483219100);
+ assertEq(vitaFast.balanceOf(alice), 0);
+
+ vm.startPrank(paulhaas);
+ vitaFast.transfer(alice, 100);
+ assertEq(vitaFast.balanceOf(paulhaas), 16942857059768483219000);
+ assertEq(vitaFast.balanceOf(alice), 100);
+ vm.stopPrank();
+
+ vm.startPrank(vitaFASTMultisig);
+ assertEq(vitaFast.totalSupply(), 1_029_555 ether);
+ assertEq(vitaFast.balanceOf(vitaFASTMultisig), 13390539642731621592709);
+ //the old IPTs allow their original owner to issue more tokens
+ vitaFast.issue(vitaFASTMultisig, 100_000 ether);
+ assertEq(vitaFast.totalSupply(), 1_129_555 ether);
+ assertEq(vitaFast.balanceOf(vitaFASTMultisig), 113390539642731621592709);
+
+ vitaFast.cap();
+ vm.expectRevert(TokenCapped.selector);
+ vitaFast.issue(vitaFASTMultisig, 100_000 ether);
+
+ /// --- same for VitaRNA, better safe than sorry.
+ address vitaRNAAddress = 0x7b66E84Be78772a3afAF5ba8c1993a1B5D05F9C2;
+ address vitaRNAMultisig = 0x452f3b60129FdB3cdc78178848c63eC23f38C80d;
+ IPToken vitaRna = IPToken(vitaRNAAddress);
+
+ assertEq(vitaRna.balanceOf(paulhaas), 514.411456805927582924 ether);
+ assertEq(vitaRna.balanceOf(alice), 0);
+
+ vm.startPrank(paulhaas);
+ vitaRna.transfer(alice, 100 ether);
+ assertEq(vitaRna.balanceOf(paulhaas), 414.411456805927582924 ether);
+ assertEq(vitaRna.balanceOf(alice), 100 ether);
+ vm.stopPrank();
+
+ vm.startPrank(vitaRNAMultisig);
+ assertEq(vitaRna.totalSupply(), 5_000_000 ether);
+ assertEq(vitaRna.balanceOf(vitaRNAMultisig), 200_000 ether);
+ vitaRna.issue(vitaRNAMultisig, 100_000 ether);
+ assertEq(vitaRna.totalSupply(), 5_100_000 ether);
+ assertEq(vitaRna.balanceOf(vitaRNAMultisig), 300_000 ether);
+
+ vitaRna.cap();
+ vm.expectRevert(TokenCapped.selector);
+ vitaRna.issue(vitaRNAMultisig, 100_000 ether);
+ }
+
+ // IPN-21: and the main reason why we're doing all the above
+ function testOldTokensCanBeIssuedByNewIPNFTHolder() public {
+ upgradeToTokenizer13();
+
+ deployCodeTo("Permissioner.sol:BlindPermissioner", "", address(tokenizer13.permissioner()));
+
+ address bob = makeAddr("bob");
+ address vitaFASTMultisig = 0xf7990CD398daFB4fe5Fd6B9228B8e6f72b296555;
+ //we're using vita fast's original abi here. It actually is call-compatible to IPToken, but this is the ultimate legacy test
+ IPToken12 vitaFAST12 = IPToken12(0x6034e0d6999741f07cb6Fb1162cBAA46a1D33d36);
+ IPToken vitaFAST13 = IPToken(0x6034e0d6999741f07cb6Fb1162cBAA46a1D33d36);
+
+ vm.startPrank(vitaFASTMultisig);
+ ipnft.transferFrom(vitaFASTMultisig, alice, 2);
+ assertEq(ipnft.ownerOf(2), alice);
+
+ vm.startPrank(alice);
+ // This is new: originally Alice *would* indeed have been able to do this:
+ vm.expectRevert(AlreadyTokenized.selector);
+ tokenizer13.tokenizeIpnft(2, 1_000_000 ether, "VITA-FAST", "imfeelingfunny", bytes(""));
+
+ assertEq(vitaFAST12.balanceOf(alice), 0);
+
+ //this *should* be possible but can't work due to the old implementation
+ vm.expectRevert(OnlyIssuerOrOwner.selector);
+ vitaFAST12.issue(alice, 1_000_000 ether);
+ //the selector of course doesnt exist on the new interface, but the implementation reverts with it:
+ vm.expectRevert(OnlyIssuerOrOwner.selector);
+ vitaFAST13.issue(alice, 1_000_000 ether);
+
+ //to issue new tokens, alice uses the Tokenizer instead:
+ tokenizer13.issue(vitaFAST13, 1_000_000 ether, alice);
+ assertEq(vitaFAST12.balanceOf(alice), 1_000_000 ether);
+
+ //due to the original implementation, the original owner can still issue tokens and we cannot do anything about it:
+ vm.startPrank(vitaFASTMultisig);
+ vitaFAST12.issue(bob, 1_000_000 ether);
+ assertEq(vitaFAST12.balanceOf(bob), 1_000_000 ether);
+ assertEq(vitaFAST13.balanceOf(bob), 1_000_000 ether);
+
+ // but they cannot do that using the tokenizer:
+ vm.expectRevert(MustControlIpnft.selector);
+ tokenizer13.issue(vitaFAST13, 1_000_000 ether, alice);
+ vm.expectRevert(MustControlIpnft.selector);
+ tokenizer13.cap(vitaFAST13);
+
+ //but they unfortunately also can cap the token:
+ vitaFAST12.cap();
+
+ vm.startPrank(alice);
+ vm.expectRevert(TokenCapped.selector);
+ tokenizer13.issue(vitaFAST13, 1_000_000 ether, alice);
+ }
+}
diff --git a/test/Forking/TokenizerFork.t.sol b/test/Forking/TokenizerFork.t.sol
deleted file mode 100644
index b9e58d9a..00000000
--- a/test/Forking/TokenizerFork.t.sol
+++ /dev/null
@@ -1,129 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.18;
-
-import "forge-std/Test.sol";
-import { console } from "forge-std/console.sol";
-
-//import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
-import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
-
-import { IPNFT } from "../../src/IPNFT.sol";
-
-import { MustOwnIpnft, AlreadyTokenized, Tokenizer } from "../../src/Tokenizer.sol";
-import { Tokenizer11 } from "../../src/helpers/test-upgrades/Tokenizer11.sol";
-import { IPToken, OnlyIssuerOrOwner, TokenCapped, Metadata } from "../../src/IPToken.sol";
-import { IPermissioner, BlindPermissioner } from "../../src/Permissioner.sol";
-
-//import { SchmackoSwap, ListingState } from "../../src/SchmackoSwap.sol";
-
-contract TokenizerForkTest is Test {
- using SafeERC20Upgradeable for IPToken;
-
- uint256 mainnetFork;
-
- string ipfsUri = "ipfs://bafkreiankqd3jvpzso6khstnaoxovtyezyatxdy7t2qzjoolqhltmasqki";
- string agreementCid = "bafkreigk5dvqblnkdniges6ft5kmuly47ebw4vho6siikzmkaovq6sjstq";
- uint256 MINTING_FEE = 0.001 ether;
- string DEFAULT_SYMBOL = "IPT-0001";
-
- address mainnetDeployer = 0x34021576F01275A429163a56908Bd02b43e2B7e1;
- address mainnetOwner = 0xCfA0F84660fB33bFd07C369E5491Ab02C449f71B;
- address mainnetTokenizer = 0x58EB89C69CB389DBef0c130C6296ee271b82f436;
- address mainnetIPNFT = 0xcaD88677CA87a7815728C72D74B4ff4982d54Fc1;
-
- // old IP Token implementation
- address vitaFASTAddress = 0x6034e0d6999741f07cb6Fb1162cBAA46a1D33d36;
-
- // https://app.safe.global/home?safe=eth:0xf7990CD398daFB4fe5Fd6B9228B8e6f72b296555
- address vitaFASTMultisig = 0xf7990CD398daFB4fe5Fd6B9228B8e6f72b296555;
-
- // paulhaas.eth
- address paulhaas = 0x45602BFBA960277bF917C1b2007D1f03d7bd29e4;
-
- // ValleyDAO multisig
- address valleyDaoMultisig = 0xD920E60b798A2F5a8332799d8a23075c9E77d5F8;
-
- // ValleyDAO IPNFT
- uint256 valleyDaoIpnftId = 3;
-
- address alice = makeAddr("alice");
-
- function setUp() public {
- mainnetFork = vm.createFork(vm.envString("MAINNET_RPC_URL"), 18968463);
- vm.selectFork(mainnetFork);
- }
-
- function testCanUpgradeErc20TokenImplementation() public {
- IPNFT ipnftMainnetInstance = IPNFT(mainnetIPNFT);
- Tokenizer11 tokenizer11 = Tokenizer11(mainnetTokenizer);
-
- vm.startPrank(mainnetDeployer);
- Tokenizer newTokenizerImplementation = new Tokenizer();
- IPToken newIPTokenImplementation = new IPToken();
- vm.stopPrank();
-
- vm.startPrank(mainnetOwner);
- bytes memory upgradeCallData = abi.encodeWithSelector(Tokenizer.setIPTokenImplementation.selector, address(newIPTokenImplementation));
- tokenizer11.upgradeToAndCall(address(newTokenizerImplementation), upgradeCallData);
- Tokenizer upgradedTokenizer = Tokenizer(mainnetTokenizer);
-
- assertEq(address(upgradedTokenizer.ipTokenImplementation()), address(newIPTokenImplementation));
-
- deployCodeTo("Permissioner.sol:BlindPermissioner", "", address(upgradedTokenizer.permissioner()));
- vm.stopPrank();
-
- vm.startPrank(alice);
- vm.expectRevert("Initializable: contract is already initialized");
- newTokenizerImplementation.initialize(IPNFT(address(0)), BlindPermissioner(address(0)));
- vm.expectRevert("Initializable: contract is already initialized");
- newIPTokenImplementation.initialize("Foo", "Bar", Metadata(2, alice, "abcde"));
- vm.stopPrank();
-
- vm.startPrank(mainnetOwner);
- vm.expectRevert("Initializable: contract is already initialized");
- upgradedTokenizer.initialize(IPNFT(address(0)), BlindPermissioner(address(0)));
- vm.expectRevert("Initializable: contract is already initialized");
- upgradedTokenizer.reinit(BlindPermissioner(address(0)));
- vm.stopPrank();
-
- assertEq(ipnftMainnetInstance.ownerOf(valleyDaoIpnftId), valleyDaoMultisig);
-
- vm.startPrank(valleyDaoMultisig);
- IPToken ipt = upgradedTokenizer.tokenizeIpnft(valleyDaoIpnftId, 1_000_000 ether, "IPT", agreementCid, "");
- assertEq(ipt.balanceOf(valleyDaoMultisig), 1_000_000 ether);
- ipt.transfer(alice, 100_000 ether);
-
- assertEq(ipt.balanceOf(valleyDaoMultisig), 900_000 ether);
-
- ipt.issue(valleyDaoMultisig, 1_000_000 ether);
- assertEq(ipt.totalSupply(), 2_000_000 ether);
- assertEq(ipt.balanceOf(valleyDaoMultisig), 1_900_000 ether);
-
- ipt.cap();
-
- vm.expectRevert(TokenCapped.selector);
- ipt.issue(valleyDaoMultisig, 100);
-
- vm.stopPrank();
-
- IPToken vitaFast = IPToken(vitaFASTAddress);
-
- assertEq(vitaFast.balanceOf(paulhaas), 16942857059768483219100);
- assertEq(vitaFast.balanceOf(alice), 0);
-
- vm.startPrank(paulhaas);
- vitaFast.transfer(alice, 100);
- assertEq(vitaFast.balanceOf(paulhaas), 16942857059768483219000);
- assertEq(vitaFast.balanceOf(alice), 100);
- vm.stopPrank();
-
- vm.startPrank(vitaFASTMultisig);
- assertEq(vitaFast.balanceOf(vitaFASTMultisig), 16940676213630533216614);
- vitaFast.issue(vitaFASTMultisig, 100_000 ether);
- assertEq(vitaFast.totalSupply(), 1129555 ether);
- assertEq(vitaFast.balanceOf(vitaFASTMultisig), 116940676213630533216614);
- vitaFast.cap();
- vm.expectRevert(TokenCapped.selector);
- vitaFast.issue(vitaFASTMultisig, 100_000 ether);
- }
-}
diff --git a/test/Permissioner.t.sol b/test/Permissioner.t.sol
index 0b8be9d3..b5400a0e 100644
--- a/test/Permissioner.t.sol
+++ b/test/Permissioner.t.sol
@@ -12,7 +12,7 @@ import { IPNFT } from "../src/IPNFT.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 { MustOwnIpnft, AlreadyTokenized, Tokenizer, ZeroAddress } from "../src/Tokenizer.sol";
+import { MustControlIpnft, AlreadyTokenized, Tokenizer, ZeroAddress } from "../src/Tokenizer.sol";
import "./helpers/MakeGnosisWallet.sol";
@@ -55,7 +55,7 @@ contract PermissionerTest is Test {
vm.startPrank(deployer);
permissioner = new TermsAcceptedPermissioner();
- tokenizer.reinit(permissioner);
+ vm.store(address(tokenizer), bytes32(uint256(3)), bytes32(uint256(uint160(address(permissioner)))));
vm.stopPrank();
}
diff --git a/test/SalesShareDistributor.t.sol b/test/SalesShareDistributor.t.sol
index ca277e44..1085acbf 100644
--- a/test/SalesShareDistributor.t.sol
+++ b/test/SalesShareDistributor.t.sol
@@ -24,11 +24,13 @@ import {
ListingMismatch,
NotClaimingYet,
UncappedToken,
- OnlyIssuer,
- InsufficientBalance
+ OnlySeller,
+ MustControlIpnft,
+ InsufficientBalance,
+ NotSalesBeneficiary
} from "../src/SalesShareDistributor.sol";
-import { IPToken, OnlyIssuerOrOwner } from "../src/IPToken.sol";
+import { IPToken } from "../src/IPToken.sol";
import { SchmackoSwap, ListingState } from "../src/SchmackoSwap.sol";
import { FakeERC20 } from "../src/helpers/FakeERC20.sol";
@@ -77,29 +79,11 @@ contract SalesShareDistributorTest is Test {
IPToken ipTokenImplementation = new IPToken();
- tokenizer = Tokenizer(
- address(
- new ERC1967Proxy(
- address(
- new Tokenizer()
- ),
- ""
- )
- )
- );
+ tokenizer = Tokenizer(address(new ERC1967Proxy(address(new Tokenizer()), "")));
tokenizer.initialize(ipnft, blindPermissioner);
tokenizer.setIPTokenImplementation(ipTokenImplementation);
- distributor = SalesShareDistributor(
- address(
- new ERC1967Proxy(
- address(
- new SalesShareDistributor()
- ),
- ""
- )
- )
- );
+ distributor = SalesShareDistributor(address(new ERC1967Proxy(address(new SalesShareDistributor()), "")));
distributor.initialize(schmackoSwap);
vm.stopPrank();
@@ -145,14 +129,12 @@ contract SalesShareDistributorTest is Test {
function testStartClaimingPhase() public {
vm.startPrank(originalOwner);
- // 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();
+ // This is maximally important to do upfront. Otherwise the new buyer could simply issue new tokens and claim it back for themselves
+ tokenContract.cap();
- assertEq(tokenContract.issuer(), originalOwner);
- vm.startPrank(originalOwner);
+ uint256 listingId = helpCreateListing(1_000_000 ether, address(distributor));
vm.expectRevert(ListingNotFulfilled.selector);
distributor.afterSale(tokenContract, listingId, blindPermissioner);
vm.stopPrank();
@@ -160,15 +142,10 @@ contract SalesShareDistributorTest is Test {
vm.startPrank(ipnftBuyer);
erc20.approve(address(schmackoSwap), 1_000_000 ether);
schmackoSwap.fulfill(listingId);
- vm.stopPrank();
assertEq(erc20.balanceOf(address(distributor)), 1_000_000 ether);
vm.startPrank(originalOwner);
- vm.expectRevert(UncappedToken.selector);
- distributor.afterSale(tokenContract, listingId, blindPermissioner);
-
- tokenContract.cap();
distributor.afterSale(tokenContract, listingId, blindPermissioner);
vm.stopPrank();
@@ -179,28 +156,23 @@ contract SalesShareDistributorTest is Test {
function testManuallyStartClaimingPhase() public {
vm.startPrank(originalOwner);
IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "MOLE", agreementCid, "");
- ipnft.safeTransferFrom(originalOwner, ipnftBuyer, 1);
- assertEq(tokenContract.issuer(), originalOwner);
tokenContract.cap();
- vm.stopPrank();
+ ipnft.safeTransferFrom(originalOwner, ipnftBuyer, 1);
vm.startPrank(ipnftBuyer);
erc20.transfer(originalOwner, 1_000_000 ether);
- vm.stopPrank();
- // only the owner can manually start the claiming phase.
+ // only the origina owner can atm manually start the claiming phase.
vm.startPrank(bob);
- vm.expectRevert(OnlyIssuer.selector);
- distributor.afterSale(tokenContract, erc20, 1_000_000 ether, blindPermissioner);
- vm.stopPrank();
+ vm.expectRevert(MustControlIpnft.selector);
+ distributor.UNSAFE_afterSale(tokenContract, erc20, 1_000_000 ether, blindPermissioner);
vm.startPrank(originalOwner);
vm.expectRevert(); // not approved
- distributor.afterSale(tokenContract, erc20, 1_000_000 ether, blindPermissioner);
+ distributor.UNSAFE_afterSale(tokenContract, erc20, 1_000_000 ether, blindPermissioner);
erc20.approve(address(distributor), 1_000_000 ether);
- distributor.afterSale(tokenContract, erc20, 1_000_000 ether, blindPermissioner);
- vm.stopPrank();
+ distributor.UNSAFE_afterSale(tokenContract, erc20, 1_000_000 ether, blindPermissioner);
assertEq(erc20.balanceOf(address(originalOwner)), 0);
(uint256 fulfilledListingId,,,) = distributor.sales(address(tokenContract));
@@ -220,11 +192,9 @@ contract SalesShareDistributorTest is Test {
vm.startPrank(ipnftBuyer);
erc20.transfer(originalOwner, 1_000_000 ether);
- vm.stopPrank();
vm.startPrank(originalOwner);
ipnft.safeTransferFrom(originalOwner, ipnftBuyer, 1);
- vm.stopPrank();
(uint8 v, bytes32 r, bytes32 s) =
vm.sign(alicePk, ECDSA.toEthSignedMessageHash(abi.encodePacked(permissioner.specificTermsV1(tokenContract))));
@@ -233,12 +203,10 @@ contract SalesShareDistributorTest is Test {
//someone must start the claiming phase first
vm.expectRevert(NotClaimingYet.selector);
distributor.claim(tokenContract, abi.encodePacked(r, s, v));
- vm.stopPrank();
vm.startPrank(originalOwner);
erc20.approve(address(distributor), 1_000_000 ether);
- distributor.afterSale(tokenContract, erc20, 1_000_000 ether, permissioner);
- vm.stopPrank();
+ distributor.UNSAFE_afterSale(tokenContract, erc20, 1_000_000 ether, permissioner);
vm.startPrank(alice);
(, uint256 amount) = distributor.claimableTokens(tokenContract, alice);
@@ -337,31 +305,30 @@ contract SalesShareDistributorTest is Test {
vm.startPrank(originalOwner);
erc20.approve(address(distributor), salesPrice);
- distributor.afterSale(tokenContract, erc20, salesPrice, blindPermissioner);
+ distributor.UNSAFE_afterSale(tokenContract, erc20, salesPrice, blindPermissioner);
vm.stopPrank();
}
function testClaimingFraud() public {
+ address anotherOwner = makeAddr("anotherOwner");
+ address unknown = makeAddr("unknown");
+
vm.startPrank(originalOwner);
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));
schmackoSwap.changeBuyerAllowance(listingId1, ipnftBuyer, true);
- vm.stopPrank();
-
- vm.deal(bob, MINTING_FEE);
- vm.startPrank(deployer);
- vm.stopPrank();
-
- vm.startPrank(bob);
+ vm.startPrank(anotherOwner);
+ vm.deal(anotherOwner, MINTING_FEE);
uint256 reservationId = ipnft.reserve();
- ipnft.mintReservation{ value: MINTING_FEE }(bob, reservationId, ipfsUri, DEFAULT_SYMBOL, "");
+ ipnft.mintReservation{ value: MINTING_FEE }(anotherOwner, reservationId, ipfsUri, DEFAULT_SYMBOL, "");
ipnft.setApprovalForAll(address(schmackoSwap), true);
IPToken tokenContract2 = tokenizer.tokenizeIpnft(2, 70_000, "MOLE", agreementCid, "");
tokenContract2.cap();
- uint256 listingId2 = schmackoSwap.list(IERC721(address(ipnft)), 2, erc20, 700 ether, address(originalOwner));
+ uint256 listingId2 = schmackoSwap.list(IERC721(address(ipnft)), 2, erc20, 700 ether, unknown);
schmackoSwap.changeBuyerAllowance(listingId2, ipnftBuyer, true);
vm.stopPrank();
@@ -369,22 +336,19 @@ contract SalesShareDistributorTest is Test {
erc20.approve(address(schmackoSwap), 1_000_000 ether);
schmackoSwap.fulfill(listingId1);
schmackoSwap.fulfill(listingId2);
- vm.stopPrank();
- vm.startPrank(bob);
- vm.expectRevert(InsufficientBalance.selector);
+ vm.startPrank(anotherOwner);
+ vm.expectRevert(NotSalesBeneficiary.selector);
distributor.afterSale(tokenContract2, listingId2, blindPermissioner);
- vm.stopPrank();
- vm.startPrank(originalOwner);
vm.expectRevert(ListingMismatch.selector);
distributor.afterSale(tokenContract1, listingId2, blindPermissioner);
- vm.expectRevert(OnlyIssuer.selector);
- distributor.afterSale(tokenContract2, listingId2, blindPermissioner);
+ vm.startPrank(originalOwner);
+ vm.expectRevert(OnlySeller.selector);
+ distributor.afterSale(tokenContract1, listingId2, blindPermissioner);
distributor.afterSale(tokenContract1, listingId1, blindPermissioner);
- vm.stopPrank();
vm.startPrank(bob);
vm.expectRevert(InsufficientBalance.selector);
diff --git a/test/SynthesizerUpgrade.t.sol b/test/SynthesizerUpgrade.t.sol
deleted file mode 100644
index 0ed9726a..00000000
--- a/test/SynthesizerUpgrade.t.sol
+++ /dev/null
@@ -1,260 +0,0 @@
-// SPDX-License-Identifier: MIT
-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 { 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, Tokenizer11 } from "../src/helpers/test-upgrades/Tokenizer11.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, InvalidSignature } from "../src/Permissioner.sol";
-import {
- IPermissioner as OldIPermissioner,
- TermsAcceptedPermissioner as OldTermsAcceptedPermissioner
-} from "../src/helpers/test-upgrades/SynthPermissioner.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;
-
- 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 will be token holders
- address alice = makeAddr("alice");
- uint256 alicePk;
-
- address bob = makeAddr("bob");
- uint256 bobPk;
-
- 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(alice, 500_000 ether);
- erc20.mint(bob, 500_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(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.startPrank(deployer);
- Tokenizer11 tokenizerImpl = new Tokenizer11();
-
- synthesizer.upgradeTo(address(tokenizerImpl));
-
- Tokenizer11 tokenizer = Tokenizer11(address(synthesizer));
-
- 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);
-
- 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();
-
- 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);
- Tokenizer11 tokenizerImpl = new Tokenizer11();
- synthesizer.upgradeTo(address(tokenizerImpl));
-
- Tokenizer11 tokenizer = Tokenizer11(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/Tokenizer.t.sol b/test/Tokenizer.t.sol
index 10688fea..e23b593f 100644
--- a/test/Tokenizer.t.sol
+++ b/test/Tokenizer.t.sol
@@ -17,14 +17,30 @@ import { IPNFT } from "../src/IPNFT.sol";
import { AcceptAllAuthorizer } from "./helpers/AcceptAllAuthorizer.sol";
import { FakeERC20 } from "../src/helpers/FakeERC20.sol";
-import { MustOwnIpnft, AlreadyTokenized, Tokenizer, ZeroAddress } from "../src/Tokenizer.sol";
+import { MustControlIpnft, AlreadyTokenized, Tokenizer, ZeroAddress } from "../src/Tokenizer.sol";
-import { IPToken, OnlyIssuerOrOwner, TokenCapped } from "../src/IPToken.sol";
+import { IPToken, TokenCapped } from "../src/IPToken.sol";
+import { IControlIPTs } from "../src/IControlIPTs.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 GovernorOfTheFuture is IControlIPTs {
+ function controllerOf(uint256 ipnftId) external view override returns (address) {
+ return address(0); //no one but me controls IPTs!
+ }
+
+ function aMajorityWantsToIssueTokensTo(IPToken ipt, uint256 amount, address receiver) public {
+ ipt.issue(receiver, amount);
+ }
+}
+
+contract TokenizerWithHandover is Tokenizer {
+ //this oc would be gated for the current IPNFT holder
+ function handoverControl(IPToken ipt, GovernorOfTheFuture governor) external onlyController(ipt) {
+ ipt.transferOwnership(address(governor));
+ }
+}
contract TokenizerTest is Test {
using SafeERC20Upgradeable for IPToken;
@@ -50,7 +66,7 @@ contract TokenizerTest is Test {
IPNFT internal ipnft;
Tokenizer internal tokenizer;
- SchmackoSwap internal schmackoSwap;
+
IPermissioner internal blindPermissioner;
FakeERC20 internal erc20;
@@ -63,8 +79,7 @@ contract TokenizerTest is Test {
ipnft.initialize();
ipnft.setAuthorizer(new AcceptAllAuthorizer());
- schmackoSwap = new SchmackoSwap();
- erc20 = new FakeERC20('Fake ERC20', 'FERC');
+ erc20 = new FakeERC20("Fake ERC20", "FERC");
erc20.mint(ipnftBuyer, 1_000_000 ether);
blindPermissioner = new BlindPermissioner();
@@ -79,7 +94,6 @@ contract TokenizerTest is Test {
vm.startPrank(originalOwner);
uint256 reservationId = ipnft.reserve();
ipnft.mintReservation{ value: MINTING_FEE }(originalOwner, reservationId, ipfsUri, DEFAULT_SYMBOL, "");
- vm.stopPrank();
}
function testSetIPTokenImplementation() public {
@@ -95,7 +109,6 @@ contract TokenizerTest is Test {
vm.startPrank(originalOwner);
vm.expectRevert("Ownable: caller is not the owner");
tokenizer.setIPTokenImplementation(newIPTokenImplementation);
- vm.stopPrank();
}
function testUrl() public {
@@ -103,7 +116,6 @@ contract TokenizerTest is Test {
IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, "");
string memory uri = tokenContract.uri();
assertGt(bytes(uri).length, 200);
- vm.stopPrank();
}
function testIssueIPToken() public {
@@ -121,27 +133,32 @@ contract TokenizerTest is Test {
vm.startPrank(originalOwner);
tokenContract.transfer(alice, 10_000);
- vm.stopPrank();
assertEq(tokenContract.balanceOf(alice), 10_000);
assertEq(tokenContract.balanceOf(originalOwner), 90_000);
assertEq(tokenContract.totalSupply(), 100_000);
}
- function testIncreaseIPToken() public {
+ function testIncreaseIPTokenSupply() public {
vm.startPrank(originalOwner);
IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, "");
tokenContract.transfer(alice, 25_000);
tokenContract.transfer(bob, 25_000);
- tokenContract.issue(originalOwner, 100_000);
- vm.stopPrank();
+ tokenContract.issue(originalOwner, 50_000);
+ tokenizer.issue(tokenContract, 50_000, originalOwner);
vm.startPrank(bob);
- vm.expectRevert(OnlyIssuerOrOwner.selector);
+ vm.expectRevert(MustControlIpnft.selector);
tokenContract.issue(bob, 12345);
- vm.stopPrank();
+ vm.expectRevert(MustControlIpnft.selector);
+ tokenizer.issue(tokenContract, 12345, bob);
+
+ vm.expectRevert(MustControlIpnft.selector);
+ tokenContract.cap();
+ vm.expectRevert(MustControlIpnft.selector);
+ tokenizer.cap(tokenContract);
assertEq(tokenContract.balanceOf(alice), 25_000);
assertEq(tokenContract.balanceOf(bob), 25_000);
@@ -149,16 +166,33 @@ contract TokenizerTest is Test {
assertEq(tokenContract.totalSupply(), 200_000);
assertEq(tokenContract.totalIssued(), 200_000);
- vm.startPrank(bob);
- vm.expectRevert(OnlyIssuerOrOwner.selector);
- tokenContract.cap();
- vm.stopPrank();
-
vm.startPrank(originalOwner);
+ // both work and cap can be called multiple times without reverting
tokenContract.cap();
+ tokenizer.cap(tokenContract);
+
vm.expectRevert(TokenCapped.selector);
- tokenContract.issue(bob, 12345);
- vm.stopPrank();
+ tokenizer.issue(tokenContract, 12345, bob);
+ }
+
+ function testIPNFTHolderControlsIPT() public {
+ vm.startPrank(originalOwner);
+ IPToken tokenContract = tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, "");
+ tokenContract.issue(bob, 50_000);
+ ipnft.transferFrom(originalOwner, alice, 1);
+
+ vm.startPrank(alice);
+ tokenContract.issue(alice, 50_000);
+ assertEq(tokenContract.balanceOf(alice), 50_000);
+
+ //the original owner *cannot* issue tokens anymore
+ //this actually worked before 1.3 since IPTs were bound to their original owner
+ vm.startPrank(originalOwner);
+ vm.expectRevert(MustControlIpnft.selector);
+ tokenContract.issue(alice, 50_000);
+
+ vm.expectRevert(MustControlIpnft.selector);
+ tokenizer.issue(tokenContract, 50_000, bob);
}
function testCanBeTokenizedOnlyOnce() public {
@@ -173,7 +207,7 @@ contract TokenizerTest is Test {
function testCannotTokenizeIfNotOwner() public {
vm.startPrank(alice);
- vm.expectRevert(MustOwnIpnft.selector);
+ vm.expectRevert(MustControlIpnft.selector);
tokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, "");
vm.stopPrank();
}
@@ -196,7 +230,7 @@ contract TokenizerTest is Test {
assertEq(tokenContract.balanceOf(address(wallet)), 100_000);
- //test the SAFE can send molecules to another account
+ //test the SAFE can send IPTs to another account
bytes memory transferCall = abi.encodeCall(tokenContract.transfer, (bob, 10_000));
bytes32 encodedTxDataHash = wallet.getTransactionHash(
address(tokenContract), 0, transferCall, Enum.Operation.Call, 80_000, 1 gwei, 20 gwei, address(0x0), payable(0x0), 0
@@ -209,8 +243,40 @@ contract TokenizerTest is Test {
wallet.execTransaction(
address(tokenContract), 0, transferCall, Enum.Operation.Call, 80_000, 1 gwei, 20 gwei, address(0x0), payable(0x0), xsignatures
);
- vm.stopPrank();
assertEq(tokenContract.balanceOf(bob), 10_000);
}
+
+ function testTokenizerCanHandoverControl() public {
+ vm.startPrank(deployer);
+ TokenizerWithHandover htokenizer = TokenizerWithHandover(address(new ERC1967Proxy(address(new TokenizerWithHandover()), "")));
+ htokenizer.initialize(ipnft, blindPermissioner);
+ htokenizer.setIPTokenImplementation(new IPToken());
+
+ vm.startPrank(originalOwner);
+ IPToken tokenContract = htokenizer.tokenizeIpnft(1, 100_000, "IPT", agreementCid, "");
+ tokenContract.issue(bob, 50_000);
+
+ vm.startPrank(deployer);
+ GovernorOfTheFuture governor = new GovernorOfTheFuture();
+ vm.stopPrank();
+
+ vm.startPrank(originalOwner);
+ htokenizer.handoverControl(tokenContract, governor);
+
+ vm.startPrank(alice); // alice controls the governor, eg by proving that a vote has occured
+ governor.aMajorityWantsToIssueTokensTo(tokenContract, 50_000, alice);
+ assertEq(tokenContract.balanceOf(alice), 50_000);
+
+ // -- from here on, *only* the new governor is in conrol
+ vm.expectRevert(MustControlIpnft.selector);
+ tokenContract.issue(alice, 50_000);
+
+ vm.startPrank(originalOwner);
+ vm.expectRevert(MustControlIpnft.selector);
+ tokenContract.issue(bob, 50_000);
+
+ vm.expectRevert(MustControlIpnft.selector);
+ htokenizer.issue(tokenContract, 50_000, bob);
+ }
}