From a80648856d3dabb587249e73b5edc19e59923f30 Mon Sep 17 00:00:00 2001 From: Stefan Adolf Date: Thu, 19 Dec 2024 10:30:41 +0100 Subject: [PATCH] adds option to locking crowdsales to trust other locked token initiators Signed-off-by: Stefan Adolf --- script/prod/RolloutV25Sale.sol | 1 + src/crowdsale/LockingCrowdSale.sol | 23 +- subgraph/abis/IPNFT.json | 19 + subgraph/abis/LockingCrowdSale.json | 863 ++++++++++++++++++++++ subgraph/abis/StakedLockingCrowdSale.json | 74 ++ subgraph/makeAbis.sh | 1 + subgraph/networks.json | 12 + subgraph/schema.graphql | 1 + test/CrowdSaleLockedStakedTest.t.sol | 4 + 9 files changed, 995 insertions(+), 3 deletions(-) create mode 100644 subgraph/abis/LockingCrowdSale.json diff --git a/script/prod/RolloutV25Sale.sol b/script/prod/RolloutV25Sale.sol index 9c07e476..ca72a000 100644 --- a/script/prod/RolloutV25Sale.sol +++ b/script/prod/RolloutV25Sale.sol @@ -32,6 +32,7 @@ contract RolloutV25LockingSale is Script { function run() public { //mainnet 0xCfA0F84660fB33bFd07C369E5491Ab02C449f71B; address moleculeDevMultisig = 0x9d5a6ae551f1117946FF6e0e86ef9A1B20C90Cb0; + address stakedLockingCrowdsale = 0xd1cE2EA7d3b0C9cAB025A4aD762FC00315141ad7; TimelockedToken timelockedTokenImplementation = TimelockedToken(0xF8F79c1E02387b0Fc9DE0945cD9A2c06F127D851); vm.startBroadcast(); diff --git a/src/crowdsale/LockingCrowdSale.sol b/src/crowdsale/LockingCrowdSale.sol index 6cc0438b..5a0dfc76 100644 --- a/src/crowdsale/LockingCrowdSale.sol +++ b/src/crowdsale/LockingCrowdSale.sol @@ -12,12 +12,16 @@ import { CrowdSale, Sale } from "./CrowdSale.sol"; error UnsupportedInitializer(); error InvalidDuration(); +interface ITrustedLockingContracts { + function lockingContracts(address) external view returns (TimelockedToken); +} + /** * @title LockingCrowdSale * @author molecule.to * @notice a fixed price sales base contract that locks the sold tokens for a configurable duration */ -contract LockingCrowdSale is CrowdSale { +contract LockingCrowdSale is CrowdSale, ITrustedLockingContracts { using SafeERC20 for IERC20Metadata; mapping(uint256 => uint256) public salesLockingDuration; @@ -25,6 +29,10 @@ contract LockingCrowdSale is CrowdSale { /// @notice map from token address to reusable TimelockedToken contracts mapping(address => TimelockedToken) public lockingContracts; + ///@notice this can be another contract registry that takes care of locking contracts + /// to reuse implementations + ITrustedLockingContracts public lockingContractTrustee; + address immutable public TIMELOCKED_TOKEN_IMPLEMENTATION; event Started(uint256 indexed saleId, address indexed issuer, Sale sale, TimelockedToken lockingToken, uint256 lockingDuration, uint16 feeBp); @@ -39,6 +47,10 @@ contract LockingCrowdSale is CrowdSale { revert UnsupportedInitializer(); } + function trustLockingContractSource(ITrustedLockingContracts _lockingContractTrustee) public onlyOwner { + lockingContractTrustee = _lockingContractTrustee; + } + /** * @notice allows anyone to create a timelocked token that's controlled by this sale contract * helpful if you want to reuse the timelocked token for your own custom schedules @@ -50,8 +62,13 @@ contract LockingCrowdSale is CrowdSale { lockedTokenContract = lockingContracts[address(underlyingToken)]; if (address(lockedTokenContract) == address(0)) { - lockedTokenContract = _makeNewLockedTokenContract(underlyingToken); - lockingContracts[address(underlyingToken)] = lockedTokenContract; + if (address(lockingContractTrustee) != address(0)) { + lockedTokenContract = lockingContractTrustee.lockingContracts(address(underlyingToken)); + } + if (address(lockedTokenContract) == address(0)) { + lockedTokenContract = _makeNewLockedTokenContract(underlyingToken); + lockingContracts[address(underlyingToken)] = lockedTokenContract; + } } } diff --git a/subgraph/abis/IPNFT.json b/subgraph/abis/IPNFT.json index b03fe6f6..23c7752b 100644 --- a/subgraph/abis/IPNFT.json +++ b/subgraph/abis/IPNFT.json @@ -187,6 +187,25 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "isPoi", + "inputs": [ + { + "name": "tokenId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "pure" + }, { "type": "function", "name": "mintReservation", diff --git a/subgraph/abis/LockingCrowdSale.json b/subgraph/abis/LockingCrowdSale.json new file mode 100644 index 00000000..b5cfba09 --- /dev/null +++ b/subgraph/abis/LockingCrowdSale.json @@ -0,0 +1,863 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "_timelockedTokenImplementation", + "type": "address", + "internalType": "contract TimelockedToken" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "TIMELOCKED_TOKEN_IMPLEMENTATION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "claim", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "permission", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "auctionTokens", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "refunds", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "claimResults", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "contribution", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "contributor", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "createOrReturnTimelockContract", + "inputs": [ + { + "name": "underlyingToken", + "type": "address", + "internalType": "contract IERC20Metadata" + } + ], + "outputs": [ + { + "name": "lockedTokenContract", + "type": "address", + "internalType": "contract TimelockedToken" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "currentFeeBp", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint16", + "internalType": "uint16" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getClaimableAmounts", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "bidder", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "auctionTokens", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "refunds", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getSaleInfo", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct SaleInfo", + "components": [ + { + "name": "state", + "type": "uint8", + "internalType": "enum SaleState" + }, + { + "name": "total", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "surplus", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "claimed", + "type": "bool", + "internalType": "bool" + }, + { + "name": "feeBp", + "type": "uint16", + "internalType": "uint16" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "lockingContractTrustee", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract ITrustedLockingContracts" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "lockingContracts", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract TimelockedToken" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "placeBid", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "biddingTokenAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "permission", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "salesLockingDuration", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setCurrentFeesBp", + "inputs": [ + { + "name": "newFeeBp", + "type": "uint16", + "internalType": "uint16" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "settle", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "startSale", + "inputs": [ + { + "name": "sale", + "type": "tuple", + "internalType": "struct Sale", + "components": [ + { + "name": "auctionToken", + "type": "address", + "internalType": "contract IERC20Metadata" + }, + { + "name": "biddingToken", + "type": "address", + "internalType": "contract IERC20Metadata" + }, + { + "name": "beneficiary", + "type": "address", + "internalType": "address" + }, + { + "name": "fundingGoal", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "salesAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "closingTime", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "permissioner", + "type": "address", + "internalType": "contract IPermissioner" + } + ] + }, + { + "name": "lockingDuration", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "saleId", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "startSale", + "inputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct Sale", + "components": [ + { + "name": "auctionToken", + "type": "address", + "internalType": "contract IERC20Metadata" + }, + { + "name": "biddingToken", + "type": "address", + "internalType": "contract IERC20Metadata" + }, + { + "name": "beneficiary", + "type": "address", + "internalType": "address" + }, + { + "name": "fundingGoal", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "salesAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "closingTime", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "permissioner", + "type": "address", + "internalType": "contract IPermissioner" + } + ] + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "trustLockingContractSource", + "inputs": [ + { + "name": "_lockingContractTrustee", + "type": "address", + "internalType": "contract ITrustedLockingContracts" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Bid", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "bidder", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Claimed", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "claimer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "claimed", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "refunded", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ClaimedAuctionTokens", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ClaimedFundingGoal", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Failed", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FeesUpdated", + "inputs": [ + { + "name": "feeBp", + "type": "uint16", + "indexed": false, + "internalType": "uint16" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LockingContractCreated", + "inputs": [ + { + "name": "lockingContract", + "type": "address", + "indexed": true, + "internalType": "contract TimelockedToken" + }, + { + "name": "underlyingToken", + "type": "address", + "indexed": true, + "internalType": "contract IERC20Metadata" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Settled", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "totalBids", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "surplus", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Started", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "issuer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sale", + "type": "tuple", + "indexed": false, + "internalType": "struct Sale", + "components": [ + { + "name": "auctionToken", + "type": "address", + "internalType": "contract IERC20Metadata" + }, + { + "name": "biddingToken", + "type": "address", + "internalType": "contract IERC20Metadata" + }, + { + "name": "beneficiary", + "type": "address", + "internalType": "address" + }, + { + "name": "fundingGoal", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "salesAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "closingTime", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "permissioner", + "type": "address", + "internalType": "contract IPermissioner" + } + ] + }, + { + "name": "lockingToken", + "type": "address", + "indexed": false, + "internalType": "contract TimelockedToken" + }, + { + "name": "lockingDuration", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "feeBp", + "type": "uint16", + "indexed": false, + "internalType": "uint16" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Started", + "inputs": [ + { + "name": "saleId", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "issuer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sale", + "type": "tuple", + "indexed": false, + "internalType": "struct Sale", + "components": [ + { + "name": "auctionToken", + "type": "address", + "internalType": "contract IERC20Metadata" + }, + { + "name": "biddingToken", + "type": "address", + "internalType": "contract IERC20Metadata" + }, + { + "name": "beneficiary", + "type": "address", + "internalType": "address" + }, + { + "name": "fundingGoal", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "salesAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "closingTime", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "permissioner", + "type": "address", + "internalType": "contract IPermissioner" + } + ] + }, + { + "name": "feeBp", + "type": "uint16", + "indexed": false, + "internalType": "uint16" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AlreadyClaimed", + "inputs": [] + }, + { + "type": "error", + "name": "BadDecimals", + "inputs": [] + }, + { + "type": "error", + "name": "BadSaleDuration", + "inputs": [] + }, + { + "type": "error", + "name": "BadSaleState", + "inputs": [ + { + "name": "expected", + "type": "uint8", + "internalType": "enum SaleState" + }, + { + "name": "actual", + "type": "uint8", + "internalType": "enum SaleState" + } + ] + }, + { + "type": "error", + "name": "BadSalesAmount", + "inputs": [] + }, + { + "type": "error", + "name": "BidTooLow", + "inputs": [] + }, + { + "type": "error", + "name": "FeesTooHigh", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidDuration", + "inputs": [] + }, + { + "type": "error", + "name": "SaleAlreadyActive", + "inputs": [] + }, + { + "type": "error", + "name": "SaleClosedForBids", + "inputs": [] + }, + { + "type": "error", + "name": "SaleNotConcluded", + "inputs": [] + }, + { + "type": "error", + "name": "SaleNotFund", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "UnsupportedInitializer", + "inputs": [] + } +] diff --git a/subgraph/abis/StakedLockingCrowdSale.json b/subgraph/abis/StakedLockingCrowdSale.json index b10408fc..64e4fdb0 100644 --- a/subgraph/abis/StakedLockingCrowdSale.json +++ b/subgraph/abis/StakedLockingCrowdSale.json @@ -1,4 +1,28 @@ [ + { + "type": "constructor", + "inputs": [ + { + "name": "_timelockedTokenImplementation", + "type": "address", + "internalType": "contract TimelockedToken" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "TIMELOCKED_TOKEN_IMPLEMENTATION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "claim", @@ -201,6 +225,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "lockingContractTrustee", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract ITrustedLockingContracts" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "lockingContracts", @@ -282,6 +319,25 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "salesStakeVestingDuration", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "salesStaking", @@ -426,6 +482,11 @@ "name": "lockingDuration", "type": "uint256", "internalType": "uint256" + }, + { + "name": "stakesVestingDuration", + "type": "uint256", + "internalType": "uint256" } ], "outputs": [ @@ -567,6 +628,19 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "trustLockingContractSource", + "inputs": [ + { + "name": "_lockingContractTrustee", + "type": "address", + "internalType": "contract ITrustedLockingContracts" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "trustVestingContract", diff --git a/subgraph/makeAbis.sh b/subgraph/makeAbis.sh index 9ad940ae..af94b686 100755 --- a/subgraph/makeAbis.sh +++ b/subgraph/makeAbis.sh @@ -9,6 +9,7 @@ cat ../out/TimelockedToken.sol/TimelockedToken.json | jq .abi > ./abis/Timelocke cat ../out/SalesShareDistributor.sol/SalesShareDistributor.json | jq .abi > ./abis/SharedSalesDistributor.json cat ../out/Permissioner.sol/TermsAcceptedPermissioner.json | jq .abi > ./abis/TermsAcceptedPermissioner.json cat ../out/StakedLockingCrowdSale.sol/StakedLockingCrowdSale.json | jq .abi > ./abis/StakedLockingCrowdSale.json +cat ../out/LockingCrowdSale.sol/LockingCrowdSale.json | jq .abi > ./abis/LockingCrowdSale.json cat ../out/CrowdSale.sol/CrowdSale.json | jq .abi > ./abis/CrowdSale.json cat ../out/Tokenizer.sol/Tokenizer.json | jq .abi > ./abis/_Tokenizer.json diff --git a/subgraph/networks.json b/subgraph/networks.json index edace0be..2a7c366d 100644 --- a/subgraph/networks.json +++ b/subgraph/networks.json @@ -16,6 +16,10 @@ "address": "0x8cA737E2cdaE1Ceb332bEf7ba9eA711a3a2f8037", "startBlock": 5300777 }, + "LockingCrowdSale": { + "address": "0xaf7358576C9F7cD84696D28702fC5ADe33cce0e9", + "startBlock": 7306191 + }, "StakedLockingCrowdSale": { "address": "0xd1cE2EA7d3b0C9cAB025A4aD762FC00315141ad7", "startBlock": 5300777 @@ -42,6 +46,10 @@ "address": "0xf0a8d23f38e9cbbe01c4ed37f23bd519b65bc6c2", "startBlock": 18490640 }, + "LockingCrowdSale": { + "address": "0xbaadf00dbaadf00dbaadf00dbaadf00dbaadf00d", + "startBlock": 21431344 + }, "StakedLockingCrowdSale": { "address": "0x35Bce29F52f51f547998717CD598068Afa2B29B7", "startBlock": 17481804 @@ -68,6 +76,10 @@ "address": "0x09635F643e140090A9A8Dcd712eD6285858ceBef", "startBlock": 0 }, + "LockingCrowdSale": { + "address": "0xbaadf00dbaadf00dbaadf00dbaadf00dbaadf00d", + "startBlock": 0 + }, "StakedLockingCrowdSale": { "address": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1", "startBlock": 0 diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 07965d53..1987904e 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -120,6 +120,7 @@ enum SaleState { enum SaleType { CROWDSALE + LOCKING_CROWDSALE STAKED_LOCKING_CROWDSALE } diff --git a/test/CrowdSaleLockedStakedTest.t.sol b/test/CrowdSaleLockedStakedTest.t.sol index e53fc6f4..51716e53 100644 --- a/test/CrowdSaleLockedStakedTest.t.sol +++ b/test/CrowdSaleLockedStakedTest.t.sol @@ -455,4 +455,8 @@ contract CrowdSaleLockedStakedTest is Test { assertEq(daoToken.balanceOf(bidder2), 1_000_000 ether); // vm.stopPrank(); } + + function skipTestReuseLockingContracts() public { + //todo: write a test that demonstrates how the locking crowdsale reuses locking contracts from the slcs + } }