Skip to content

Commit

Permalink
Merge branch 'main' into buncha-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
clemlak authored Feb 5, 2025
2 parents 501d9e6 + 5e45370 commit b8b87c1
Show file tree
Hide file tree
Showing 23 changed files with 513 additions and 2,384 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
[submodule "lib/v3-core"]
path = lib/v3-core
url = https://github.com/Uniswap/v3-core
[submodule "lib/universal-router"]
path = lib/universal-router
url = https://github.com/Uniswap/universal-router
407 changes: 0 additions & 407 deletions broadcast/DeployFactory.sol/480/run-1733853114.json

This file was deleted.

407 changes: 0 additions & 407 deletions broadcast/DeployFactory.sol/480/run-1733853350.json

This file was deleted.

407 changes: 0 additions & 407 deletions broadcast/DeployFactory.sol/480/run-latest.json

This file was deleted.

497 changes: 0 additions & 497 deletions broadcast/DeployFactoryUnichainSepolia.sol/1301/run-1736280200.json

This file was deleted.

497 changes: 0 additions & 497 deletions broadcast/DeployFactoryUnichainSepolia.sol/1301/run-latest.json

This file was deleted.

1 change: 1 addition & 0 deletions lib/universal-router
Submodule universal-router added at 3fc2b4
6 changes: 5 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ permit2/=lib/v4-periphery/lib/permit2/

@v3-periphery/=lib/v3-periphery/contracts/
@v3-core/=lib/v3-core/contracts
@uniswap/v3-core/=lib/v3-core/
@uniswap/v3-core/=lib/v3-core/
@universal-router=lib/universal-router/contracts/


@uniswap/v2-core/contracts/interfaces/=src/interfaces/
49 changes: 24 additions & 25 deletions script/DeployFactoryUnichainSepolia.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,64 @@ pragma solidity ^0.8.24;

import { Script, console2 } from "forge-std/Script.sol";
import { IPoolManager } from "@v4-core/interfaces/IPoolManager.sol";
import { PoolManager } from "@v4-core/PoolManager.sol";
import { Deployers } from "@v4-core-test/utils/Deployers.sol";
import { StateView } from "@v4-periphery/lens/StateView.sol";
import { V4Quoter } from "@v4-periphery/lens/V4Quoter.sol";
import { PoolSwapTest } from "@v4-core/test/PoolSwapTest.sol";
import { Airlock, ModuleState } from "src/Airlock.sol";
import { TokenFactory } from "src/TokenFactory.sol";
import { GovernanceFactory } from "src/GovernanceFactory.sol";
import { UniswapV2Migrator, IUniswapV2Router02, IUniswapV2Factory } from "src/UniswapV2Migrator.sol";
import { UniswapV3Initializer, IUniswapV3Factory } from "src/UniswapV3Initializer.sol";
import { UniswapV4Initializer, DopplerDeployer } from "src/UniswapV4Initializer.sol";
import { CustomRouter2 } from "test/shared/CustomRouter2.sol";
import { UniversalRouter } from "@universal-router/UniversalRouter.sol";

contract DeployDopplerV3FactoryUnichainSepolia is Script {
contract DeployDopplerV3FactoryUnichainSepolia is Script, Deployers {
Airlock airlock;
TokenFactory tokenFactory;
UniswapV4Initializer uniswapV4Initializer;
UniswapV3Initializer uniswapV3Initializer;
GovernanceFactory governanceFactory;
UniswapV2Migrator uniswapV2LiquidityMigrator;
DopplerDeployer dopplerDeployer;
CustomRouter2 router;

function setUp() public { }
UniversalRouter universalRouter;
StateView stateView;

address constant uniRouterV2 = 0x920b806E40A00E02E7D2b94fFc89860fDaEd3640;
address constant uniFactoryV2 = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;

address constant manager = 0xC81462Fec8B23319F288047f8A03A57682a35C1A;
address constant quoter = 0xfe6Cf50c4cfe801dd2AEf9c1B3ce24f551944df8;
address constant stateView = 0xdE04C804dc75E90D8a64e5589092a1D6692EFA45;
address constant uniRouter = 0x9e5A52f57b3038F1B8EeE45F28b3C1967e22799C;

address v3CoreFactory = 0x1F98431c8aD98523631AE4a59f267346ea31F984;

string public constant ENV_PRIVATE_KEY = "PRIVATE_KEY";

function run() public {
uint256 pk = vm.envUint(ENV_PRIVATE_KEY);

vm.startBroadcast(pk);

address account = vm.addr(pk);

manager = PoolManager(0x00B036B58a818B1BC34d502D3fE730Db729e62AC);
console2.log("manager: ", address(manager), " as Address");

airlock = new Airlock(address(account));
console2.log("Airlock: ", address(airlock));
console2.log("airlock: ", address(airlock), " as Address");
tokenFactory = new TokenFactory(address(airlock));
console2.log("TokenFactory: ", address(tokenFactory));
console2.log("tokenFactory: ", address(tokenFactory), " as Address");
dopplerDeployer = new DopplerDeployer(IPoolManager(manager));
console2.log("dopplerDeployer: ", address(dopplerDeployer), " as Address");
uniswapV4Initializer = new UniswapV4Initializer(address(airlock), IPoolManager(manager), dopplerDeployer);
console2.log("UniswapV4Initializer: ", address(uniswapV4Initializer));
console2.log("v4Initializer: ", address(uniswapV4Initializer), " as Address");
uniswapV3Initializer = new UniswapV3Initializer(address(airlock), IUniswapV3Factory(v3CoreFactory));
console2.log("UniswapV3Initializer: ", address(uniswapV3Initializer));
console2.log("v3Initializer: ", address(uniswapV3Initializer), " as Address");
governanceFactory = new GovernanceFactory(address(airlock));
console2.log("GovernanceFactory: ", address(governanceFactory));
console2.log("governanceFactory: ", address(governanceFactory), " as Address");
uniswapV2LiquidityMigrator = new UniswapV2Migrator(
address(airlock), IUniswapV2Factory(uniFactoryV2), IUniswapV2Router02(uniRouterV2), address(0xb055)
address(airlock), IUniswapV2Factory(uniFactoryV2), IUniswapV2Router02(uniRouterV2), address(account)
);
console2.log("Migrator: ", address(uniswapV2LiquidityMigrator));
console2.log("StateView: ", address(stateView));
console2.log("Quoter: ", address(quoter));
router = new CustomRouter2(PoolSwapTest(uniRouter), V4Quoter(quoter));
console2.log("CustomRouter: ", address(router));
console2.log(airlock.owner());
console2.log("migrator: ", address(uniswapV2LiquidityMigrator), " as Address");
universalRouter = UniversalRouter(payable(0xf70536B3bcC1bD1a972dc186A2cf84cC6da6Be5D));
console2.log("universalRouter: ", address(universalRouter), " as Address");
stateView = StateView(0xc199F1072a74D4e905ABa1A84d9a45E2546B6222);
console2.log("stateView: ", address(stateView), " as Address");

address[] memory modules = new address[](5);
modules[0] = address(tokenFactory);
Expand Down
12 changes: 6 additions & 6 deletions src/Airlock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ contract Airlock is Ownable {
createData.liquidityMigrator.initialize(asset, createData.numeraire, createData.liquidityMigratorData);
DERC20(asset).lockPool(migrationPool);

uint256 excessAsset = ERC20(asset).balanceOf(address(this));

if (excessAsset > 0) {
ERC20(asset).safeTransfer(timelock, excessAsset);
}

getAssetData[asset] = AssetData({
numeraire: createData.numeraire,
timelock: timelock,
Expand Down Expand Up @@ -223,12 +229,6 @@ contract Airlock is Ownable {
uint256 total0 = balance0 - fees0;
uint256 total1 = balance1 - fees1;

if (token0 == asset) {
total0 += assetData.totalSupply - assetData.numTokensToSell;
} else {
total1 += assetData.totalSupply - assetData.numTokensToSell;
}

if (token0 == address(0)) {
SafeTransferLib.safeTransferETH(address(assetData.liquidityMigrator), total0);
} else {
Expand Down
120 changes: 92 additions & 28 deletions src/DERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ error MintingNotStartedYet();
/// @dev Thrown when trying to mint more than the yearly cap
error ExceedsYearlyMintCap();

/// @dev Thrown when there is no amount to mint
error NoMintableAmount();

/// @dev Thrown when trying to transfer tokens into the pool while it is locked
error PoolLocked();

Expand All @@ -28,15 +31,24 @@ error MaxPreMintPerAddressExceeded(uint256 amount, uint256 limit);
/// @dev Thrown when trying to premint more than the maximum allowed in total
error MaxTotalPreMintExceeded(uint256 amount, uint256 limit);

/// @dev Thrown when trying to mint more than the maximum allowed in total
error MaxTotalVestedExceeded(uint256 amount, uint256 limit);

/// @dev Thrown when trying to release tokens before the vesting period has started
error VestingNotStartedYet();

/// @dev Thrown when trying to set the mint rate to a value higher than the maximum allowed
error MaxYearlyMintRateExceeded(uint256 amount, uint256 limit);

/// @dev Max amount of tokens that can be pre-minted per address (% expressed in WAD)
uint256 constant MAX_PRE_MINT_PER_ADDRESS_WAD = 0.01 ether;

/// @dev Max amount of tokens that can be pre-minted in total (% expressed in WAD)
uint256 constant MAX_TOTAL_PRE_MINT_WAD = 0.1 ether;

/// @dev Maximum amount of tokens that can be minted in a year (% expressed in WAD)
uint256 constant MAX_YEARLY_MINT_RATE_WAD = 0.02 ether;

/**
* @notice Vesting data for a specific address
* @param totalAmount Total amount of vested tokens
Expand All @@ -49,29 +61,29 @@ struct VestingData {

/// @custom:security-contact [email protected]
contract DERC20 is ERC20, ERC20Votes, ERC20Permit, Ownable {
/// @notice Minting token will be possible after this timestamp
uint256 public immutable mintStartDate;

/// @notice Maximum amount of tokens that can be minted in a year
uint256 public immutable yearlyMintCap;
/// @notice Timestamp of the start of the vesting period
uint256 public immutable vestingStart;

/// @notice Duration of the vesting period (in seconds)
uint256 public immutable vestingDuration;

/// @notice Timestamp of the start of the vesting period
uint256 public vestingStart;
/// @notice Total amount of vested tokens
uint256 public immutable vestedTotalAmount;

/// @notice Address of the liquidity pool
address public pool;

/// @notice Whether the pool can receive tokens (unlocked) or not
bool public isPoolUnlocked;

/// @notice Timestamp of the start of the current yearly period
/// @notice Maximum rate of tokens that can be minted in a year
uint256 public yearlyMintRate;

/// @notice Timestamp of the start of the current year
uint256 public currentYearStart;

/// @notice Amount of tokens minted in the current year
uint256 public currentAnnualMint;
/// @notice Timestamp of the last inflation mint
uint256 public lastMintTimestamp;

/// @notice Uniform Resource Identifier (URI)
string public tokenURI;
Expand All @@ -90,7 +102,7 @@ contract DERC20 is ERC20, ERC20Votes, ERC20Permit, Ownable {
* @param initialSupply Initial supply of the token
* @param recipient Address receiving the initial supply
* @param owner_ Address receivin the ownership of the token
* @param yearlyMintCap_ Maximum amount of token that can be minted in a year
* @param yearlyMintRate_ Maximum inflation rate of token in a year
* @param vestingDuration_ Duration of the vesting period (in seconds)
* @param recipients_ Array of addresses receiving vested tokens
* @param amounts_ Array of amounts of tokens to be vested
Expand All @@ -102,14 +114,18 @@ contract DERC20 is ERC20, ERC20Votes, ERC20Permit, Ownable {
uint256 initialSupply,
address recipient,
address owner_,
uint256 yearlyMintCap_,
uint256 yearlyMintRate_,
uint256 vestingDuration_,
address[] memory recipients_,
uint256[] memory amounts_,
string memory tokenURI_
) ERC20(name_, symbol_) ERC20Permit(name_) Ownable(owner_) {
mintStartDate = block.timestamp + 365 days;
yearlyMintCap = yearlyMintCap_;
require(
yearlyMintRate_ <= MAX_YEARLY_MINT_RATE_WAD,
MaxYearlyMintRateExceeded(yearlyMintRate_, MAX_YEARLY_MINT_RATE_WAD)
);
yearlyMintRate = yearlyMintRate_;
vestingStart = block.timestamp;
vestingDuration = vestingDuration_;
tokenURI = tokenURI_;

Expand All @@ -132,6 +148,9 @@ contract DERC20 is ERC20, ERC20Votes, ERC20Permit, Ownable {

uint256 maxTotalPreMint = initialSupply * MAX_TOTAL_PRE_MINT_WAD / 1 ether;
require(vestedTokens <= maxTotalPreMint, MaxTotalPreMintExceeded(vestedTokens, maxTotalPreMint));
require(vestedTokens < initialSupply, MaxTotalVestedExceeded(vestedTokens, initialSupply));

vestedTotalAmount = vestedTokens;

if (vestedTokens > 0) {
_mint(address(this), vestedTokens);
Expand All @@ -154,26 +173,71 @@ contract DERC20 is ERC20, ERC20Votes, ERC20Permit, Ownable {
/// @notice Unlocks the pool, allowing it to receive tokens
function unlockPool() external onlyOwner {
isPoolUnlocked = true;
vestingStart = block.timestamp;
currentYearStart = lastMintTimestamp = block.timestamp;
}

/**
* @notice Mints `amount` of tokens to the address `to`
* @param to Address receiving the minted tokens
* @param value Amount of tokens to mint
* @notice Mints inflation tokens to the owner
*/
function mint(address to, uint256 value) external onlyOwner {
require(block.timestamp >= mintStartDate, MintingNotStartedYet());
function mintInflation() public {
require(currentYearStart != 0, MintingNotStartedYet());

uint256 mintableAmount;
uint256 yearMint;
uint256 timeLeftInCurrentYear;
uint256 supply = totalSupply();
uint256 currentYearStart_ = currentYearStart;
uint256 lastMintTimestamp_ = lastMintTimestamp;
uint256 yearlyMintRate_ = yearlyMintRate;
// Handle any outstanding full years and updates to maintain inflation rate
while (block.timestamp > currentYearStart_ + 365 days) {
timeLeftInCurrentYear = (currentYearStart_ + 365 days - lastMintTimestamp_);
yearMint = (supply * yearlyMintRate_ * timeLeftInCurrentYear) / (1 ether * 365 days);
supply += yearMint;
mintableAmount += yearMint;
currentYearStart_ += 365 days;
lastMintTimestamp_ = currentYearStart_;
}

if (block.timestamp >= currentYearStart + 365 days) {
currentYearStart = block.timestamp;
currentAnnualMint = 0;
// Handle partial current year
if (block.timestamp > lastMintTimestamp_) {
uint256 partialYearMint =
(supply * yearlyMintRate_ * (block.timestamp - lastMintTimestamp_)) / (1 ether * 365 days);
mintableAmount += partialYearMint;
}

require(currentAnnualMint + value <= yearlyMintCap, ExceedsYearlyMintCap());
currentAnnualMint += value;
require(mintableAmount > 0, NoMintableAmount());

currentYearStart = currentYearStart_;
lastMintTimestamp = block.timestamp;
_mint(owner(), mintableAmount);
}

/**
* @notice Burns `amount` of tokens from the address `owner`
* @param amount Amount of tokens to burn
*/
function burn(
uint256 amount
) external onlyOwner {
_burn(owner(), amount);
}

/**
* @notice Updates the maximum rate of tokens that can be minted in a year
* @param newMintRate New maximum rate of tokens that can be minted in a year
*/
function updateMintRate(
uint256 newMintRate
) external onlyOwner {
// Inflation can't be more than 2% of token supply per year
require(newMintRate <= MAX_YEARLY_MINT_RATE_WAD, "New mint rate exceeds the maximum allowed");

if (currentYearStart != 0 && (block.timestamp - lastMintTimestamp) != 0) {
mintInflation();
}

_mint(to, value);
yearlyMintRate = newMintRate;
}

/**
Expand All @@ -199,9 +263,9 @@ contract DERC20 is ERC20, ERC20Votes, ERC20Permit, Ownable {

/// @inheritdoc Nonces
function nonces(
address owner
address owner_
) public view override(ERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
return super.nonces(owner_);
}

/// @inheritdoc ERC20
Expand Down
Loading

0 comments on commit b8b87c1

Please sign in to comment.