@zoralabs/coins 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +1 -0
- package/.turbo/turbo-build.log +114 -109
- package/.turbo/turbo-update-contract-version.log +22 -0
- package/CHANGELOG.md +28 -0
- package/abis/BadImpl.json +15 -0
- package/abis/BalanceDeltaLibrary.json +15 -0
- package/abis/BaseCoin.json +1442 -0
- package/abis/BaseCoinDeployHook.json +78 -0
- package/abis/BaseHook.json +897 -0
- package/abis/BaseTest.json +13 -91
- package/abis/BeforeSwapDeltaLibrary.json +15 -0
- package/abis/BuySupplyWithSwapRouterHook.json +126 -0
- package/abis/Coin.json +48 -92
- package/abis/CoinConstants.json +65 -0
- package/abis/CoinTest.json +13 -91
- package/abis/CoinV4.json +1664 -0
- package/abis/CurrencyLibrary.json +25 -0
- package/abis/DeployHooks.json +9 -0
- package/abis/DopplerUniswapV3Test.json +13 -91
- package/abis/FactoryTest.json +13 -91
- package/abis/FakeHookNoInterface.json +21 -0
- package/abis/HookDeployer.json +68 -0
- package/abis/Hooks.json +28 -0
- package/abis/HooksTest.json +651 -0
- package/abis/IAllowanceTransfer.json +486 -0
- package/abis/ICoin.json +25 -1
- package/abis/ICoinDeployHook.json +31 -0
- package/abis/IContractMetadata.json +28 -0
- package/abis/IEIP712.json +15 -0
- package/abis/IEIP712_v4.json +15 -0
- package/abis/IERC20Minimal.json +172 -0
- package/abis/IERC6909Claims.json +288 -0
- package/abis/IERC721Permit_v4.json +88 -0
- package/abis/IExtsload.json +64 -0
- package/abis/IExttload.json +40 -0
- package/abis/IHasAfterCoinDeploy.json +31 -0
- package/abis/IHasContractName.json +15 -0
- package/abis/IHookDeployer.json +42 -0
- package/abis/IHooks.json +789 -0
- package/abis/IImmutableState.json +15 -0
- package/abis/IMulticall_v4.json +21 -0
- package/abis/INotifier.json +187 -0
- package/abis/IPermit2.json +865 -0
- package/abis/IPermit2Forwarder.json +138 -0
- package/abis/IPoolInitializer_v4.json +53 -0
- package/abis/IPoolManager.json +1286 -0
- package/abis/IPositionManager.json +712 -0
- package/abis/IProtocolFees.json +174 -0
- package/abis/ISignatureTransfer.json +394 -0
- package/abis/ISubscriber.json +89 -0
- package/abis/ISwapRouter.json +82 -0
- package/abis/IUnorderedNonce.json +44 -0
- package/abis/IV4Router.json +47 -0
- package/abis/IZoraFactory.json +144 -0
- package/abis/IZoraV4CoinHook.json +83 -0
- package/abis/ImmutableState.json +36 -0
- package/abis/LPFeeLibrary.json +65 -0
- package/abis/MultiOwnableTest.json +13 -91
- package/abis/Simulate.json +0 -91
- package/abis/UniV3BuySell.json +12 -0
- package/abis/UniV3Errors.json +32 -0
- package/abis/UpgradeFactoryImpl.json +9 -0
- package/abis/UpgradesTest.json +604 -0
- package/abis/ZoraFactoryImpl.json +111 -0
- package/abis/ZoraV4CoinHook.json +989 -0
- package/addresses/8453.json +2 -1
- package/addresses/84532.json +4 -3
- package/dist/index.cjs +125 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +120 -56
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +212 -464
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/package/wagmiGenerated.ts +122 -66
- package/package.json +4 -4
- package/script/CoinsDeployerBase.sol +32 -0
- package/script/DeployHooks.s.sol +22 -0
- package/script/Simulate.s.sol +3 -2
- package/script/UpgradeCoinImpl.sol +2 -2
- package/script/UpgradeFactoryImpl.s.sol +23 -0
- package/src/Coin.sol +35 -342
- package/src/ZoraFactoryImpl.sol +73 -45
- package/src/hooks/BaseCoinDeployHook.sol +62 -0
- package/src/hooks/BuySupplyWithSwapRouterHook.sol +78 -0
- package/src/interfaces/ICoin.sol +5 -1
- package/src/interfaces/ICoinDeployHook.sol +8 -0
- package/src/interfaces/ISwapRouter.sol +1 -35
- package/src/interfaces/IZoraFactory.sol +52 -0
- package/src/{utils → libs}/CoinConstants.sol +6 -6
- package/src/libs/CoinLegacy.sol +4 -4
- package/src/libs/CoinLegacyMarket.sol +182 -0
- package/src/libs/CoinSetupV3.sol +111 -0
- package/src/libs/UniV3BuySell.sol +449 -0
- package/src/libs/UniV3Errors.sol +11 -0
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/Coin.t.sol +20 -17
- package/test/CoinDopplerUniV3.t.sol +7 -17
- package/test/Factory.t.sol +4 -3
- package/test/Hooks.t.sol +274 -0
- package/test/Upgrades.t.sol +67 -0
- package/test/utils/BaseTest.sol +18 -3
- package/wagmi.config.ts +6 -9
- package/src/libs/CoinSetup.sol +0 -37
- /package/abis/{CoinSetup.json → CoinSetupV3.json} +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import {ICoin} from "../interfaces/ICoin.sol";
|
|
5
|
+
import {IZoraFactory} from "../interfaces/IZoraFactory.sol";
|
|
6
|
+
import {ICoinDeployHook} from "../interfaces/ICoinDeployHook.sol";
|
|
7
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
8
|
+
|
|
9
|
+
/// @title Immutable State
|
|
10
|
+
/// @notice A collection of immutable state variables, commonly used across multiple contracts
|
|
11
|
+
contract ImmutableState {
|
|
12
|
+
IZoraFactory public immutable factory;
|
|
13
|
+
|
|
14
|
+
/// @notice Thrown when the caller is not Factory
|
|
15
|
+
error NotFactory();
|
|
16
|
+
|
|
17
|
+
/// @notice Thrown when a zero address is used
|
|
18
|
+
error AddressZero();
|
|
19
|
+
|
|
20
|
+
/// @notice Only allow calls from the PoolManager contract
|
|
21
|
+
modifier onlyFactory() {
|
|
22
|
+
require(msg.sender == address(factory), NotFactory());
|
|
23
|
+
_;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
constructor(IZoraFactory _factory) {
|
|
27
|
+
require(address(_factory) != address(0), AddressZero());
|
|
28
|
+
factory = _factory;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface IHasAfterCoinDeploy {
|
|
33
|
+
/// @notice Hook that is called after a coin is deployed
|
|
34
|
+
/// @param sender The address that called the factory
|
|
35
|
+
/// @param coin The coin that was deployed
|
|
36
|
+
/// @param hookData The data passed to the hook
|
|
37
|
+
/// @return hookDataOut The data returned by the hook
|
|
38
|
+
/// @dev This function can only be called by the factory
|
|
39
|
+
function afterCoinDeploy(address sender, ICoin coin, bytes calldata hookData) external payable returns (bytes memory);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// @title Base Hook
|
|
43
|
+
/// @notice abstract contract for coin deploy hook implementations
|
|
44
|
+
abstract contract BaseCoinDeployHook is ImmutableState, IHasAfterCoinDeploy, IERC165 {
|
|
45
|
+
/// @notice Thrown when a hook method is not implemented
|
|
46
|
+
error HookNotImplemented();
|
|
47
|
+
|
|
48
|
+
constructor(IZoraFactory _factory) ImmutableState(_factory) {}
|
|
49
|
+
|
|
50
|
+
/// @inheritdoc IHasAfterCoinDeploy
|
|
51
|
+
function afterCoinDeploy(address sender, ICoin coin, bytes calldata hookData) external payable onlyFactory returns (bytes memory) {
|
|
52
|
+
return _afterCoinDeploy(sender, coin, hookData);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function _afterCoinDeploy(address, ICoin, bytes calldata) internal virtual returns (bytes memory) {
|
|
56
|
+
revert HookNotImplemented();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
|
|
60
|
+
return interfaceId == type(IHasAfterCoinDeploy).interfaceId;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {BaseCoinDeployHook} from "./BaseCoinDeployHook.sol";
|
|
5
|
+
import {ICoin} from "../interfaces/ICoin.sol";
|
|
6
|
+
import {IZoraFactory} from "../interfaces/IZoraFactory.sol";
|
|
7
|
+
import {ISwapRouter} from "../interfaces/ISwapRouter.sol";
|
|
8
|
+
import {IWETH} from "../interfaces/IWETH.sol";
|
|
9
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
10
|
+
|
|
11
|
+
/// @title BuySupplyWithSwapRouter
|
|
12
|
+
/// @notice A hook that buys supply for a coin that is priced in an erc20 token with ETH, using a Uniswap SwapRouter.
|
|
13
|
+
/// Supports both single-hop and multi-hop swaps using uniswap v3
|
|
14
|
+
contract BuySupplyWithSwapRouterHook is BaseCoinDeployHook {
|
|
15
|
+
ISwapRouter immutable swapRouter;
|
|
16
|
+
|
|
17
|
+
error Erc20NotReceived();
|
|
18
|
+
error InvalidSwapRouterCall();
|
|
19
|
+
error SwapReverted(bytes error);
|
|
20
|
+
error CoinBalanceNot0(uint256 balance);
|
|
21
|
+
|
|
22
|
+
constructor(IZoraFactory _factory, address _swapRouter) BaseCoinDeployHook(_factory) {
|
|
23
|
+
swapRouter = ISwapRouter(_swapRouter);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// @notice Hook that buys supply for a coin that is priced in an erc20 token with ETH, using a Uniswap SwapRouter.
|
|
27
|
+
/// Returns abi encoded (uint256 amountCurrency, uint256 coinsPurchased) - amountCurrency is the amount of currency received from the swap and sent to the coin for the purchase,
|
|
28
|
+
/// and coinsPurchased is the amount of coins purchased using the amountCurrency that was received from the swap
|
|
29
|
+
function _afterCoinDeploy(address, ICoin coin, bytes calldata hookData) internal override returns (bytes memory) {
|
|
30
|
+
address currency = coin.currency();
|
|
31
|
+
|
|
32
|
+
(address buyRecipient, bytes memory swapRouterCall) = abi.decode(hookData, (address, bytes));
|
|
33
|
+
|
|
34
|
+
uint256 amountCurrency = _handleSwap(currency, swapRouterCall);
|
|
35
|
+
|
|
36
|
+
uint256 coinsPurchased = _handleBuy(buyRecipient, coin, amountCurrency);
|
|
37
|
+
|
|
38
|
+
return abi.encode(amountCurrency, coinsPurchased);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function _handleSwap(address currency_, bytes memory swapRouterCall) internal returns (uint256 amountCurrency) {
|
|
42
|
+
// call the swap router, with the msg.value
|
|
43
|
+
_validateSwapRouterCall(swapRouterCall);
|
|
44
|
+
|
|
45
|
+
(bool success, bytes memory result) = address(swapRouter).call{value: msg.value}(swapRouterCall);
|
|
46
|
+
|
|
47
|
+
require(success, SwapReverted(result));
|
|
48
|
+
|
|
49
|
+
amountCurrency = abi.decode(result, (uint256));
|
|
50
|
+
|
|
51
|
+
// validate that this contract received the correct amount of currency
|
|
52
|
+
require(IERC20(currency_).balanceOf(address(this)) == amountCurrency, Erc20NotReceived());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function _validateSwapRouterCall(bytes memory swapRouterCall) internal pure {
|
|
56
|
+
// validate that the swap router call is valid - only exactInput and exactInputSingle are supported
|
|
57
|
+
|
|
58
|
+
bytes4 selector = _getSelectorFromCall(swapRouterCall);
|
|
59
|
+
|
|
60
|
+
require(selector == ISwapRouter.exactInput.selector || selector == ISwapRouter.exactInputSingle.selector, InvalidSwapRouterCall());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function _getSelectorFromCall(bytes memory _call) internal pure returns (bytes4 selector) {
|
|
64
|
+
assembly {
|
|
65
|
+
selector := mload(add(_call, 32))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function _handleBuy(address buyRecipient, ICoin coin, uint256 amountCurrency) internal returns (uint256 coinsPurchased) {
|
|
70
|
+
IERC20(coin.currency()).approve(address(coin), amountCurrency);
|
|
71
|
+
|
|
72
|
+
(, coinsPurchased) = coin.buy(buyRecipient, amountCurrency, 0, 0, address(0));
|
|
73
|
+
|
|
74
|
+
// make sure that this contract has no balance of the coin remaining
|
|
75
|
+
uint256 coinBalance = IERC20(address(coin)).balanceOf(address(this));
|
|
76
|
+
require(coinBalance == 0, CoinBalanceNot0(coinBalance));
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/interfaces/ICoin.sol
CHANGED
|
@@ -50,7 +50,7 @@ interface ICoin is IERC165, IERC7572, IDopplerErrors {
|
|
|
50
50
|
error EthTransferFailed();
|
|
51
51
|
|
|
52
52
|
/// @notice Thrown when an operation is attempted by an entity other than the pool
|
|
53
|
-
error OnlyPool();
|
|
53
|
+
error OnlyPool(address sender, address pool);
|
|
54
54
|
|
|
55
55
|
/// @notice Thrown when an operation is attempted by an entity other than WETH
|
|
56
56
|
error OnlyWeth();
|
|
@@ -215,4 +215,8 @@ interface ICoin is IERC165, IERC7572, IDopplerErrors {
|
|
|
215
215
|
/// @notice Returns the address of the platform referrer
|
|
216
216
|
/// @return The platform referrer's address
|
|
217
217
|
function platformReferrer() external view returns (address);
|
|
218
|
+
|
|
219
|
+
/// @notice Returns the address of the currency
|
|
220
|
+
/// @return The currency's address
|
|
221
|
+
function currency() external view returns (address);
|
|
218
222
|
}
|
|
@@ -1,38 +1,4 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
/// @title Router token swapping functionality
|
|
7
|
-
/// @notice Functions for swapping tokens via Uniswap V3
|
|
8
|
-
interface ISwapRouter is IUniswapV3SwapCallback {
|
|
9
|
-
struct ExactInputSingleParams {
|
|
10
|
-
address tokenIn;
|
|
11
|
-
address tokenOut;
|
|
12
|
-
uint24 fee;
|
|
13
|
-
address recipient;
|
|
14
|
-
uint256 amountIn;
|
|
15
|
-
uint256 amountOutMinimum;
|
|
16
|
-
uint160 sqrtPriceLimitX96;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
struct ExactOutputSingleParams {
|
|
20
|
-
address tokenIn;
|
|
21
|
-
address tokenOut;
|
|
22
|
-
uint24 fee;
|
|
23
|
-
address recipient;
|
|
24
|
-
uint256 amountOut;
|
|
25
|
-
uint256 amountInMaximum;
|
|
26
|
-
uint160 sqrtPriceLimitX96;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/// @notice Swaps `amountIn` of one token for as much as possible of another token
|
|
30
|
-
/// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
|
|
31
|
-
/// @return amountOut The amount of the received token
|
|
32
|
-
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
|
|
33
|
-
|
|
34
|
-
/// @notice Swaps as little as possible of one token for `amountOut` of another token
|
|
35
|
-
/// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
|
|
36
|
-
/// @return amountIn The amount of the input token
|
|
37
|
-
function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);
|
|
38
|
-
}
|
|
4
|
+
import {ISwapRouter} from "@zoralabs/shared-contracts/interfaces/uniswap/ISwapRouter.sol";
|
|
@@ -32,6 +32,26 @@ interface IZoraFactory {
|
|
|
32
32
|
/// @notice Thrown when ETH is sent with a transaction but the currency is not WETH
|
|
33
33
|
error EthTransferInvalid();
|
|
34
34
|
|
|
35
|
+
/// @notice Creates a new coin contract
|
|
36
|
+
/// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
|
|
37
|
+
/// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
|
|
38
|
+
/// @param uri The coin metadata uri
|
|
39
|
+
/// @param name The name of the coin
|
|
40
|
+
/// @param symbol The symbol of the coin
|
|
41
|
+
/// @param poolConfig The config parameters for the Uniswap v3 pool; `abi.encode(address currency, int24 tickLower, int24 tickUpper, uint16 numDiscoveryPositions, uint256 maxDiscoverySupplyShare)`
|
|
42
|
+
/// @param platformReferrer The address of the platform referrer
|
|
43
|
+
/// @param orderSize The order size for the first buy; must match msg.value for ETH/WETH pairs
|
|
44
|
+
function deploy(
|
|
45
|
+
address payoutRecipient,
|
|
46
|
+
address[] memory owners,
|
|
47
|
+
string memory uri,
|
|
48
|
+
string memory name,
|
|
49
|
+
string memory symbol,
|
|
50
|
+
bytes memory poolConfig,
|
|
51
|
+
address platformReferrer,
|
|
52
|
+
uint256 orderSize
|
|
53
|
+
) external payable returns (address, uint256);
|
|
54
|
+
|
|
35
55
|
/// @notice Creates a new coin contract
|
|
36
56
|
/// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
|
|
37
57
|
/// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
|
|
@@ -53,4 +73,36 @@ interface IZoraFactory {
|
|
|
53
73
|
int24 tickLower,
|
|
54
74
|
uint256 orderSize
|
|
55
75
|
) external payable returns (address, uint256);
|
|
76
|
+
|
|
77
|
+
/// @notice Creates a new coin contract with an optional hook that runs after the coin is deployed
|
|
78
|
+
/// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
|
|
79
|
+
/// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
|
|
80
|
+
/// @param uri The coin metadata uri
|
|
81
|
+
/// @param name The name of the coin
|
|
82
|
+
/// @param symbol The symbol of the coin
|
|
83
|
+
/// @param poolConfig The config parameters for the Uniswap v3 pool; `abi.encode(address currency, int24 tickLower, int24 tickUpper, uint16 numDiscoveryPositions, uint256 maxDiscoverySupplyShare)`
|
|
84
|
+
/// @param platformReferrer The address of the platform referrer
|
|
85
|
+
/// @param hook The address of the hook to run after the coin is deployed
|
|
86
|
+
/// @param hookData The data to pass to the hook
|
|
87
|
+
/// @return coin The address of the deployed coin
|
|
88
|
+
/// @return hookDataOut The data returned by the hook
|
|
89
|
+
function deployWithHook(
|
|
90
|
+
address payoutRecipient,
|
|
91
|
+
address[] memory owners,
|
|
92
|
+
string memory uri,
|
|
93
|
+
string memory name,
|
|
94
|
+
string memory symbol,
|
|
95
|
+
bytes memory poolConfig,
|
|
96
|
+
address platformReferrer,
|
|
97
|
+
address hook,
|
|
98
|
+
bytes calldata hookData
|
|
99
|
+
) external payable returns (address coin, bytes memory hookDataOut);
|
|
100
|
+
|
|
101
|
+
/// @notice Thrown when the hook is invalid
|
|
102
|
+
error InvalidHook();
|
|
103
|
+
|
|
104
|
+
/// @notice Occurs when attempting to upgrade to a contract with a name that doesn't match the current contract's name
|
|
105
|
+
/// @param currentName The name of the current contract
|
|
106
|
+
/// @param newName The name of the contract being upgraded to
|
|
107
|
+
error UpgradeToMismatchedContractName(string currentName, string newName);
|
|
56
108
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
library CoinConstants {
|
|
5
5
|
/// @notice The maximum total supply
|
|
6
6
|
/// @dev Set to 1 billion coins with 18 decimals
|
|
7
7
|
uint256 public constant MAX_TOTAL_SUPPLY = 1_000_000_000e18;
|
|
8
8
|
|
|
9
9
|
/// @notice The number of coins allocated to the liquidity pool
|
|
10
10
|
/// @dev 990 million coins
|
|
11
|
-
uint256
|
|
11
|
+
uint256 public constant POOL_LAUNCH_SUPPLY = 990_000_000e18;
|
|
12
12
|
|
|
13
13
|
/// @notice The number of coins rewarded to the creator
|
|
14
14
|
/// @dev 10 million coins
|
|
15
|
-
uint256
|
|
15
|
+
uint256 public constant CREATOR_LAUNCH_REWARD = 10_000_000e18;
|
|
16
16
|
|
|
17
17
|
/// @notice The minimum order size allowed for trades
|
|
18
18
|
/// @dev Set to 0.0000001 ETH to prevent dust transactions
|
|
@@ -40,13 +40,13 @@ abstract contract CoinConstants {
|
|
|
40
40
|
|
|
41
41
|
/// @notice The percentage of the LP fee allocated to creators
|
|
42
42
|
/// @dev 5000 basis points = 50% of the 1% LP FEE
|
|
43
|
-
uint256
|
|
43
|
+
uint256 public constant CREATOR_MARKET_REWARD_BPS = 5000;
|
|
44
44
|
|
|
45
45
|
/// @notice The percentage of the LP fee allocated to platform referrers
|
|
46
46
|
/// @dev 2500 basis points = 25% of the 1% LP FEE
|
|
47
|
-
uint256
|
|
47
|
+
uint256 public constant PLATFORM_REFERRER_MARKET_REWARD_BPS = 2500;
|
|
48
48
|
|
|
49
49
|
/// @notice The percentage of the LP fee allocated to the Doppler protocol
|
|
50
50
|
/// @dev 500 basis points = 5% of the 1% LP FEE
|
|
51
|
-
uint256
|
|
51
|
+
uint256 public constant DOPPLER_MARKET_REWARD_BPS = 500;
|
|
52
52
|
}
|
package/src/libs/CoinLegacy.sol
CHANGED
|
@@ -35,14 +35,14 @@ library CoinLegacy {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function calculatePositions(bool isCoinToken0,
|
|
38
|
+
function calculatePositions(bool isCoinToken0, int24 tickLower, int24 tickUpper) internal pure returns (LpPosition[] memory positions) {
|
|
39
39
|
positions = new LpPosition[](1);
|
|
40
40
|
|
|
41
|
-
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ?
|
|
42
|
-
uint160 farSqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ?
|
|
41
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ? tickLower : tickUpper);
|
|
42
|
+
uint160 farSqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ? tickUpper : tickLower);
|
|
43
43
|
uint128 liquidity = isCoinToken0
|
|
44
44
|
? LiquidityAmounts.getLiquidityForAmount0(sqrtPriceX96, farSqrtPriceX96, MarketConstants.POOL_LAUNCH_SUPPLY)
|
|
45
45
|
: LiquidityAmounts.getLiquidityForAmount1(sqrtPriceX96, farSqrtPriceX96, MarketConstants.POOL_LAUNCH_SUPPLY);
|
|
46
|
-
positions[0] = LpPosition({tickLower:
|
|
46
|
+
positions[0] = LpPosition({tickLower: tickLower, tickUpper: tickUpper, liquidity: liquidity});
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
5
|
+
import {IUniswapV3Factory} from "../interfaces/IUniswapV3Factory.sol";
|
|
6
|
+
import {IUniswapV3Pool} from "../interfaces/IUniswapV3Pool.sol";
|
|
7
|
+
import {ISwapRouter} from "../interfaces/ISwapRouter.sol";
|
|
8
|
+
import {PoolConfiguration, ICoin} from "../interfaces/ICoin.sol";
|
|
9
|
+
import {TickMath} from "../utils/uniswap/TickMath.sol";
|
|
10
|
+
import {MarketConstants} from "./MarketConstants.sol";
|
|
11
|
+
import {LiquidityAmounts} from "../utils/uniswap/LiquidityAmounts.sol";
|
|
12
|
+
import {LpPosition} from "../types/LpPosition.sol";
|
|
13
|
+
import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
|
|
14
|
+
import {UniV3Errors} from "./UniV3Errors.sol";
|
|
15
|
+
|
|
16
|
+
library CoinLegacyMarket {
|
|
17
|
+
struct State {
|
|
18
|
+
address poolAddress;
|
|
19
|
+
uint160 sqrtPriceX96;
|
|
20
|
+
PoolConfiguration poolConfiguration;
|
|
21
|
+
address pairedCurrency;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
struct MarketConfig {
|
|
25
|
+
address uniswapv3Factory;
|
|
26
|
+
address weth;
|
|
27
|
+
address pairedCurrency;
|
|
28
|
+
int24 tickLower;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function _validateMarketConfig(MarketConfig memory marketConfig) internal pure {
|
|
32
|
+
if (marketConfig.uniswapv3Factory == address(0)) {
|
|
33
|
+
revert UniV3Errors.InvalidUniswapV3Factory();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (marketConfig.weth == address(0)) {
|
|
37
|
+
revert UniV3Errors.InvalidWeth();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
address weth = marketConfig.weth;
|
|
41
|
+
address currency = marketConfig.pairedCurrency;
|
|
42
|
+
int24 tickLower = marketConfig.tickLower;
|
|
43
|
+
|
|
44
|
+
// If WETH is the pool's currency, validate the lower tick
|
|
45
|
+
if ((currency == weth || currency == address(0)) && tickLower > MarketConstants.LP_TICK_LOWER_WETH) {
|
|
46
|
+
revert ICoin.InvalidWethLowerTick();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function _isCoinToken0(address coin, address currency) internal pure returns (bool isCoinToken0, address token0, address token1) {
|
|
51
|
+
token0 = coin < currency ? coin : currency;
|
|
52
|
+
token1 = token1 = coin < currency ? currency : coin;
|
|
53
|
+
isCoinToken0 = token0 == coin;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function setupMarket(bytes memory _marketConfig, address coin) internal returns (bytes memory) {
|
|
57
|
+
MarketConfig memory marketConfig = abi.decode(_marketConfig, (MarketConfig));
|
|
58
|
+
_validateMarketConfig(marketConfig);
|
|
59
|
+
|
|
60
|
+
(bool isCoinToken0, address token0, address token1) = _isCoinToken0(coin, marketConfig.pairedCurrency);
|
|
61
|
+
|
|
62
|
+
(uint160 sqrtPriceX96, PoolConfiguration memory poolConfiguration) = setupPool(
|
|
63
|
+
isCoinToken0,
|
|
64
|
+
marketConfig.pairedCurrency,
|
|
65
|
+
marketConfig.tickLower,
|
|
66
|
+
marketConfig.weth
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
address poolAddress = _createPool(token0, token1, sqrtPriceX96, marketConfig.uniswapv3Factory);
|
|
70
|
+
|
|
71
|
+
LpPosition[] memory positions = calculatePositions(isCoinToken0, poolConfiguration);
|
|
72
|
+
|
|
73
|
+
_mintPositions(positions, poolAddress);
|
|
74
|
+
|
|
75
|
+
State memory state = State({
|
|
76
|
+
poolAddress: poolAddress,
|
|
77
|
+
sqrtPriceX96: sqrtPriceX96,
|
|
78
|
+
poolConfiguration: poolConfiguration,
|
|
79
|
+
pairedCurrency: marketConfig.pairedCurrency
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return abi.encode(state);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buy(bytes memory _state, address recipient, uint256 orderSize, uint256 minAmountOut, bytes memory tradeData) internal returns (uint256, uint256) {
|
|
86
|
+
State memory state = abi.decode(_state, (State));
|
|
87
|
+
(uint160 sqrtPriceLimitX96, address swapRouter, ) = abi.decode(tradeData, (uint160, address, address));
|
|
88
|
+
|
|
89
|
+
// Calculate the trade reward
|
|
90
|
+
// uint256 tradeReward = _calculateReward(orderSize, TOTAL_FEE_BPS);
|
|
91
|
+
|
|
92
|
+
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
|
|
93
|
+
tokenIn: state.pairedCurrency,
|
|
94
|
+
tokenOut: address(this),
|
|
95
|
+
fee: MarketConstants.LP_FEE,
|
|
96
|
+
recipient: recipient,
|
|
97
|
+
amountIn: orderSize,
|
|
98
|
+
amountOutMinimum: minAmountOut,
|
|
99
|
+
sqrtPriceLimitX96: sqrtPriceLimitX96
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
uint256 amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
|
|
103
|
+
|
|
104
|
+
return (orderSize, amountOut);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function sell(bytes memory _state, uint256 orderSize, uint256 minAmountOut, bytes memory tradeData) internal returns (uint256, uint256) {
|
|
108
|
+
State memory state = abi.decode(_state, (State));
|
|
109
|
+
(uint160 sqrtPriceLimitX96, address swapRouter) = abi.decode(tradeData, (uint160, address));
|
|
110
|
+
|
|
111
|
+
// Approve the swap router to spend the coin
|
|
112
|
+
IERC20(address(this)).approve(swapRouter, orderSize);
|
|
113
|
+
|
|
114
|
+
// Set the swap parameters
|
|
115
|
+
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
|
|
116
|
+
tokenIn: address(this),
|
|
117
|
+
tokenOut: state.pairedCurrency,
|
|
118
|
+
fee: MarketConstants.LP_FEE,
|
|
119
|
+
recipient: address(this),
|
|
120
|
+
amountIn: orderSize,
|
|
121
|
+
amountOutMinimum: minAmountOut,
|
|
122
|
+
sqrtPriceLimitX96: sqrtPriceLimitX96
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Execute the swap
|
|
126
|
+
uint256 amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
|
|
127
|
+
|
|
128
|
+
return (orderSize, amountOut);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// @dev Creates the Uniswap V3 pool for the coin/currency pair
|
|
132
|
+
function _createPool(address token0, address token1, uint160 sqrtPriceX96, address v3Factory) internal returns (address pool) {
|
|
133
|
+
pool = IUniswapV3Factory(v3Factory).createPool(token0, token1, MarketConstants.LP_FEE);
|
|
134
|
+
|
|
135
|
+
// This pool should be new, if it has already been initialized
|
|
136
|
+
// then we will fail the creation step prompting the user to try again.
|
|
137
|
+
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function setupPool(
|
|
141
|
+
bool isCoinToken0,
|
|
142
|
+
address currency,
|
|
143
|
+
int24 tickLower_,
|
|
144
|
+
address weth
|
|
145
|
+
) internal pure returns (uint160 sqrtPriceX96, PoolConfiguration memory poolConfiguration) {
|
|
146
|
+
// If WETH is the pool's currency, validate the lower tick
|
|
147
|
+
if ((currency == weth || currency == address(0)) && tickLower_ > MarketConstants.LP_TICK_LOWER_WETH) {
|
|
148
|
+
revert ICoin.InvalidWethLowerTick();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
int24 savedTickLower = isCoinToken0 ? tickLower_ : -MarketConstants.LP_TICK_UPPER;
|
|
152
|
+
int24 savedTickUpper = isCoinToken0 ? MarketConstants.LP_TICK_UPPER : -tickLower_;
|
|
153
|
+
|
|
154
|
+
sqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ? savedTickLower : savedTickUpper);
|
|
155
|
+
|
|
156
|
+
poolConfiguration = PoolConfiguration({
|
|
157
|
+
version: CoinConfigurationVersions.LEGACY_POOL_VERSION,
|
|
158
|
+
tickLower: savedTickLower,
|
|
159
|
+
tickUpper: savedTickUpper,
|
|
160
|
+
numPositions: 1,
|
|
161
|
+
maxDiscoverySupplyShare: 0
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function calculatePositions(bool isCoinToken0, PoolConfiguration memory poolConfiguration) internal pure returns (LpPosition[] memory positions) {
|
|
166
|
+
positions = new LpPosition[](1);
|
|
167
|
+
|
|
168
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ? poolConfiguration.tickLower : poolConfiguration.tickUpper);
|
|
169
|
+
uint160 farSqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ? poolConfiguration.tickUpper : poolConfiguration.tickLower);
|
|
170
|
+
uint128 liquidity = isCoinToken0
|
|
171
|
+
? LiquidityAmounts.getLiquidityForAmount0(sqrtPriceX96, farSqrtPriceX96, MarketConstants.POOL_LAUNCH_SUPPLY)
|
|
172
|
+
: LiquidityAmounts.getLiquidityForAmount1(sqrtPriceX96, farSqrtPriceX96, MarketConstants.POOL_LAUNCH_SUPPLY);
|
|
173
|
+
positions[0] = LpPosition({tickLower: poolConfiguration.tickLower, tickUpper: poolConfiguration.tickUpper, liquidity: liquidity});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// @dev Mints the calculated liquidity positions into the Uniswap V3 pool
|
|
177
|
+
function _mintPositions(LpPosition[] memory lbpPositions, address poolAddress) internal {
|
|
178
|
+
for (uint256 i; i < lbpPositions.length; i++) {
|
|
179
|
+
IUniswapV3Pool(poolAddress).mint(address(this), lbpPositions[i].tickLower, lbpPositions[i].tickUpper, lbpPositions[i].liquidity, "");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {PoolConfiguration} from "../interfaces/ICoin.sol";
|
|
5
|
+
import {CoinLegacy} from "./CoinLegacy.sol";
|
|
6
|
+
import {CoinDopplerUniV3} from "./CoinDopplerUniV3.sol";
|
|
7
|
+
import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
|
|
8
|
+
import {LpPosition} from "../types/LpPosition.sol";
|
|
9
|
+
import {IUniswapV3Factory} from "../interfaces/IUniswapV3Factory.sol";
|
|
10
|
+
import {MarketConstants} from "./MarketConstants.sol";
|
|
11
|
+
import {IUniswapV3Pool} from "../interfaces/IUniswapV3Pool.sol";
|
|
12
|
+
|
|
13
|
+
struct UniV3Config {
|
|
14
|
+
address weth;
|
|
15
|
+
address v3Factory;
|
|
16
|
+
address airlock;
|
|
17
|
+
address swapRouter;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct CoinV3Config {
|
|
21
|
+
address currency;
|
|
22
|
+
PoolConfiguration poolConfiguration;
|
|
23
|
+
address poolAddress;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
library CoinSetupV3 {
|
|
27
|
+
error InvalidPoolVersion();
|
|
28
|
+
|
|
29
|
+
function setupPool(
|
|
30
|
+
bytes memory poolConfig_,
|
|
31
|
+
UniV3Config memory uniswapV3Config,
|
|
32
|
+
address coin
|
|
33
|
+
) internal returns (address currency, address poolAddress, PoolConfiguration memory poolConfiguration) {
|
|
34
|
+
// Extract version and currency from pool config
|
|
35
|
+
(uint8 version, address currency_) = abi.decode(poolConfig_, (uint8, address));
|
|
36
|
+
|
|
37
|
+
// Store the currency, defaulting to WETH if address(0)
|
|
38
|
+
currency = currency_ == address(0) ? uniswapV3Config.weth : currency_;
|
|
39
|
+
|
|
40
|
+
// Sort token addresses for Uniswap V3 pool creation
|
|
41
|
+
bool isCoinToken0 = _sortTokens(coin, currency);
|
|
42
|
+
address token0 = isCoinToken0 ? coin : currency;
|
|
43
|
+
address token1 = isCoinToken0 ? currency : coin;
|
|
44
|
+
|
|
45
|
+
// Configure the pool with appropriate version
|
|
46
|
+
uint160 sqrtPriceX96;
|
|
47
|
+
(sqrtPriceX96, poolConfiguration) = setupPoolWithVersion(version, poolConfig_, isCoinToken0, uniswapV3Config.weth);
|
|
48
|
+
|
|
49
|
+
// Create the pool
|
|
50
|
+
poolAddress = _createPool(token0, token1, sqrtPriceX96, uniswapV3Config.v3Factory);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// @dev Deploys the Uniswap V3 pool and mints initial liquidity based on the pool configuration
|
|
54
|
+
function deployLiquidity(address coin, address currency, PoolConfiguration memory poolConfiguration, address poolAddress) internal {
|
|
55
|
+
// Calculate and mint positions
|
|
56
|
+
LpPosition[] memory positions = calculatePositions(coin, currency, poolConfiguration);
|
|
57
|
+
_mintPositions(positions, poolAddress);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Helper function to sort tokens and determine if coin is token0
|
|
61
|
+
function _sortTokens(address coin, address currency) private pure returns (bool isCoinToken0) {
|
|
62
|
+
return coin < currency;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setupPoolWithVersion(
|
|
66
|
+
uint8 version,
|
|
67
|
+
bytes memory poolConfig_,
|
|
68
|
+
bool isCoinToken0,
|
|
69
|
+
address weth
|
|
70
|
+
) internal pure returns (uint160 sqrtPriceX96, PoolConfiguration memory poolConfiguration) {
|
|
71
|
+
if (version == CoinConfigurationVersions.LEGACY_POOL_VERSION) {
|
|
72
|
+
(sqrtPriceX96, poolConfiguration) = CoinLegacy.setupPool(isCoinToken0, poolConfig_, weth);
|
|
73
|
+
} else if (version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
|
|
74
|
+
(sqrtPriceX96, poolConfiguration) = CoinDopplerUniV3.setupPool(isCoinToken0, poolConfig_);
|
|
75
|
+
} else {
|
|
76
|
+
revert InvalidPoolVersion();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function calculatePositions(
|
|
81
|
+
address coin,
|
|
82
|
+
address currency,
|
|
83
|
+
PoolConfiguration memory poolConfiguration
|
|
84
|
+
) internal pure returns (LpPosition[] memory positions) {
|
|
85
|
+
// Create the pool
|
|
86
|
+
bool isCoinToken0 = _sortTokens(coin, currency);
|
|
87
|
+
if (poolConfiguration.version == CoinConfigurationVersions.LEGACY_POOL_VERSION) {
|
|
88
|
+
positions = CoinLegacy.calculatePositions(isCoinToken0, poolConfiguration.tickLower, poolConfiguration.tickUpper);
|
|
89
|
+
} else if (poolConfiguration.version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
|
|
90
|
+
positions = CoinDopplerUniV3.calculatePositions(isCoinToken0, poolConfiguration);
|
|
91
|
+
} else {
|
|
92
|
+
revert InvalidPoolVersion();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// @dev Mints the calculated liquidity positions into the Uniswap V3 pool
|
|
97
|
+
function _mintPositions(LpPosition[] memory lbpPositions, address poolAddress) internal {
|
|
98
|
+
for (uint256 i; i < lbpPositions.length; i++) {
|
|
99
|
+
IUniswapV3Pool(poolAddress).mint(address(this), lbpPositions[i].tickLower, lbpPositions[i].tickUpper, lbpPositions[i].liquidity, "");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// @dev Creates the Uniswap V3 pool for the coin/currency pair
|
|
104
|
+
function _createPool(address token0, address token1, uint160 sqrtPriceX96, address v3Factory) internal returns (address pool) {
|
|
105
|
+
pool = IUniswapV3Factory(v3Factory).createPool(token0, token1, MarketConstants.LP_FEE);
|
|
106
|
+
|
|
107
|
+
// This pool should be new, if it has already been initialized
|
|
108
|
+
// then we will fail the creation step prompting the user to try again.
|
|
109
|
+
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
|
|
110
|
+
}
|
|
111
|
+
}
|