diff --git a/package.json b/package.json index 5ea8eaf1..b46c7164 100644 --- a/package.json +++ b/package.json @@ -31,16 +31,26 @@ "postinstall": "husky install", "precommit": "lint-staged", "vercel": "yarn workspace @se-2/nextjs vercel", - "vercel:yolo": "yarn workspace @se-2/nextjs vercel:yolo" + "vercel:yolo": "yarn workspace @se-2/nextjs vercel:yolo", + "produce-signature": "node --loader ts-node/esm scripts/produceSignature.ts" }, "packageManager": "yarn@3.2.3", "devDependencies": { + "@types/node": "^22.7.4", "axios": "^1.4.0", "form-data": "^4.0.0", "husky": "^8.0.1", - "lint-staged": "^13.0.3" + "lint-staged": "^13.0.3", + "ts-node": "latest", + "typescript": "^5.6.2" }, "resolutions": { "usehooks-ts@^2.7.2": "patch:usehooks-ts@npm:^2.7.2#./.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch" + }, + "type": "module", + "dependencies": { + "@wagmi/core": "^2.13.8", + "dotenv": "^16.4.5", + "viem": "2.x" } } diff --git a/packages/hardhat/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributor.sol b/packages/hardhat/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributor.sol new file mode 100644 index 00000000..62d8c099 --- /dev/null +++ b/packages/hardhat/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributor.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import "@openzeppelin/contracts/utils/Strings.sol"; + +import {IFeeLevelJudge} from "../IFeeLevelJudge.sol"; +import "../factory/ContinuousVestingInitializable.sol"; +import "../../utilities/AccessVerifier.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import "../../config/INetworkConfig.sol"; + +contract ContinuousVestingMerkleDistributor_v_5_0 is Initializable, ContinuousVestingInitializable, AccessVerifier { + using Address for address payable; + using SafeERC20 for IERC20; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + INetworkConfig networkConfig; + // denominator used to determine size of fee bips + uint256 constant feeFractionDenominator = 10000; + + constructor() { + _disableInitializers(); + } + + function initialize( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // (deprecated) the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig + ) public initializer { + __ContinuousVesting_init( + _token, _total, _uri, _start, _cliff, _end, _maxDelayTime, uint160(uint256(_merkleRoot)), _owner + ); + + _transferOwnership(_owner); + + networkConfig = _networkConfig; + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge(networkConfig.getStakingAddress()); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + + // TODO: reduce duplication with other contracts + uint256 feeAmount = (_total * feeLevel) / feeFractionDenominator; + if (_autoPull) { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), _total + feeAmount); + + _token.approve(address(this), 0); + _token.approve(address(this), feeAmount); + _token.safeTransferFrom(address(this), networkConfig.getFeeRecipient(), feeAmount); + } else { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), feeAmount); + } + } + + function NAME() external pure override returns (string memory) { + return "ContinuousVestingMerkleInitializable"; + } + + function VERSION() external pure override returns (uint256) { + return 5; + } + + modifier validSignature(uint256 totalAmount, uint64 expiresAt, bytes memory signature) { + verifyAccessSignature(networkConfig.getAccessAuthorityAddress(), _msgSender(), totalAmount, expiresAt, signature); + + _; + } + + function claim( + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) + external + payable + validSignature(totalAmount, expiresAt, signature) + nonReentrant + { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(baseCurrencyValue >= platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(_msgSender()).sendValue(msg.value - feeAmountInWei); + + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + // TODO: reduce duplication between other contracts + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + // TODO: reduce duplication between other contracts + // Get a positive token price from a chainlink oracle + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/packages/hardhat/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributorFactory.sol b/packages/hardhat/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributorFactory.sol new file mode 100644 index 00000000..3ca0fb3b --- /dev/null +++ b/packages/hardhat/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributorFactory.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import {INetworkConfig} from "../../config/INetworkConfig.sol"; +import {ContinuousVestingMerkleDistributor_v_5_0} from "./ContinuousVestingMerkleDistributor.sol"; + +contract ContinuousVestingMerkleDistributorFactory_v_5_0 { + using Address for address payable; + + address private immutable i_implementation; + address[] public distributors; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + event DistributorDeployed(address indexed distributor); + + constructor(address implementation) { + i_implementation = implementation; + } + + function _getSalt( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) private pure returns (bytes32) { + return keccak256(abi.encode( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + )); + } + + function deployDistributor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public payable returns (ContinuousVestingMerkleDistributor_v_5_0 distributor) { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(_networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = _networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 nativeBaseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(nativeBaseCurrencyValue >= _platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((_platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + _platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(msg.sender).sendValue(msg.value - feeAmountInWei); + + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + distributor = + ContinuousVestingMerkleDistributor_v_5_0(Clones.cloneDeterministic(i_implementation, salt)); + distributors.push(address(distributor)); + + emit DistributorDeployed(address(distributor)); + + distributor.initialize(_token, _total, _uri, _start, _cliff, _end, _merkleRoot, _maxDelayTime, _owner, _feeOrSupplyHolder, _autoPull, _networkConfig); + + return distributor; + } + + function getImplementation() public view returns (address) { + return i_implementation; + } + + function predictDistributorAddress( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public view returns (address) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + return Clones.predictDeterministicAddress(i_implementation, salt, address(this)); + } + + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/packages/hardhat/contracts/claim/distributor-v5/TrancheVestingMerkleDistributor.sol b/packages/hardhat/contracts/claim/distributor-v5/TrancheVestingMerkleDistributor.sol new file mode 100644 index 00000000..92f6a3cd --- /dev/null +++ b/packages/hardhat/contracts/claim/distributor-v5/TrancheVestingMerkleDistributor.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import "@openzeppelin/contracts/utils/Strings.sol"; + +import {IFeeLevelJudge} from "../IFeeLevelJudge.sol"; +import "../../utilities/AccessVerifier.sol"; +import "../factory/ContinuousVestingInitializable.sol"; +import "../factory/TrancheVestingInitializable.sol"; +import "../factory/MerkleSetInitializable.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import "../../config/INetworkConfig.sol"; + +contract TrancheVestingMerkleDistributor_v_5_0 is + Initializable, + TrancheVestingInitializable, + AccessVerifier +{ + using Address for address payable; + using SafeERC20 for IERC20; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; // number of decimals for ETH + INetworkConfig networkConfig; + // denominator used to determine size of fee bips + uint256 constant feeFractionDenominator = 10000; + constructor() { + _disableInitializers(); + } + + function initialize( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig + ) public initializer { + __TrancheVesting_init(_token, _total, _uri, _tranches, _maxDelayTime, uint160(uint256(_merkleRoot)), _owner); + + _transferOwnership(_owner); + + networkConfig = _networkConfig; + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge(networkConfig.getStakingAddress()); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + + // TODO: reduce duplication with other contracts + uint256 feeAmount = (_total * feeLevel) / feeFractionDenominator; + if (_autoPull) { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), _total + feeAmount); + + _token.approve(address(this), 0); + _token.approve(address(this), feeAmount); + _token.safeTransferFrom(address(this), networkConfig.getFeeRecipient(), feeAmount); + } else { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), feeAmount); + } + } + + function NAME() external pure override returns (string memory) { + return "TrancheVestingMerkleDistributor"; + } + + function VERSION() external pure override returns (uint256) { + return 5; + } + + modifier validSignature(uint256 totalAmount, uint64 expiresAt, bytes memory signature) { + verifyAccessSignature(networkConfig.getAccessAuthorityAddress(), _msgSender(), totalAmount, expiresAt, signature); + + _; + } + + function claim( + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) + external + payable + validSignature(totalAmount, expiresAt, signature) + nonReentrant + { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(baseCurrencyValue >= platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(_msgSender()).sendValue(msg.value - feeAmountInWei); + + // effects + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + // TODO: reduce duplication between other contracts + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + // TODO: reduce duplication between other contracts + // Get a positive token price from a chainlink oracle + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/packages/hardhat/contracts/claim/distributor-v5/TrancheVestingMerkleDistributorFactory.sol b/packages/hardhat/contracts/claim/distributor-v5/TrancheVestingMerkleDistributorFactory.sol new file mode 100644 index 00000000..56ed2bd1 --- /dev/null +++ b/packages/hardhat/contracts/claim/distributor-v5/TrancheVestingMerkleDistributorFactory.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import {TrancheVestingMerkleDistributor_v_5_0, Tranche} from "./TrancheVestingMerkleDistributor.sol"; +import {INetworkConfig} from "../../config/INetworkConfig.sol"; + +contract TrancheVestingMerkleDistributorFactory_v_5_0 { + using Address for address payable; + + address private immutable i_implementation; + address[] public distributors; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + event DistributorDeployed(address indexed distributor); + + constructor(address implementation) { + i_implementation = implementation; + } + + function _getSalt( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) private pure returns (bytes32) { + return keccak256(abi.encode( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + )); + } + + function deployDistributor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public payable returns (TrancheVestingMerkleDistributor_v_5_0 distributor) { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(_networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = _networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 nativeBaseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(nativeBaseCurrencyValue >= _platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((_platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + _platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(msg.sender).sendValue(msg.value - feeAmountInWei); + + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + distributor = + TrancheVestingMerkleDistributor_v_5_0(Clones.cloneDeterministic(i_implementation, salt)); + distributors.push(address(distributor)); + + emit DistributorDeployed(address(distributor)); + + distributor.initialize(_token, _total, _uri, _tranches, _merkleRoot, _maxDelayTime, _owner, _feeOrSupplyHolder, _autoPull, _networkConfig); + + return distributor; + } + + function getImplementation() public view returns (address) { + return i_implementation; + } + + function predictDistributorAddress( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public view returns (address) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + return Clones.predictDeterministicAddress(i_implementation, salt, address(this)); + } + + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/packages/hardhat/contracts/config/INetworkConfig.sol b/packages/hardhat/contracts/config/INetworkConfig.sol index abf1349e..0b5a4477 100644 --- a/packages/hardhat/contracts/config/INetworkConfig.sol +++ b/packages/hardhat/contracts/config/INetworkConfig.sol @@ -6,4 +6,5 @@ interface INetworkConfig { function getStakingAddress() external view returns (address); function getNativeTokenPriceOracleAddress() external view returns (address); function getNativeTokenPriceOracleHeartbeat() external view returns (uint256); + function getAccessAuthorityAddress() external view returns (address); } diff --git a/packages/hardhat/contracts/config/NetworkConfig.sol b/packages/hardhat/contracts/config/NetworkConfig.sol index 4acf061e..1e56625a 100644 --- a/packages/hardhat/contracts/config/NetworkConfig.sol +++ b/packages/hardhat/contracts/config/NetworkConfig.sol @@ -10,14 +10,16 @@ contract NetworkConfig is OwnableUpgradeable, INetworkConfig { bool private initialized; address public nativeTokenPriceOracleAddress; uint256 public nativeTokenPriceOracleHeartbeat; + address public accessAuthorityAddress; - function initialize(address payable _feeRecipient, address _stakingAddress, address _nativeTokenPriceOracleAddress, uint256 _nativeTokenPriceOracleHeartbeat) public initializer { + function initialize(address payable _feeRecipient, address _stakingAddress, address _nativeTokenPriceOracleAddress, uint256 _nativeTokenPriceOracleHeartbeat, address _accessAuthorityAddress) public initializer { require(!initialized, "Contract instance has already been initialized"); initialized = true; feeRecipient = _feeRecipient; stakingAddress = _stakingAddress; nativeTokenPriceOracleAddress = _nativeTokenPriceOracleAddress; nativeTokenPriceOracleHeartbeat = _nativeTokenPriceOracleHeartbeat; + accessAuthorityAddress = _accessAuthorityAddress; __Ownable_init(); } @@ -36,4 +38,8 @@ contract NetworkConfig is OwnableUpgradeable, INetworkConfig { function getNativeTokenPriceOracleHeartbeat() external view returns (uint256) { return nativeTokenPriceOracleHeartbeat; } + + function getAccessAuthorityAddress() external view returns (address) { + return accessAuthorityAddress; + } } diff --git a/packages/hardhat/contracts/sale/v4/FlatPriceSale.sol b/packages/hardhat/contracts/sale/v4/FlatPriceSale.sol new file mode 100644 index 00000000..a4e7bdd8 --- /dev/null +++ b/packages/hardhat/contracts/sale/v4/FlatPriceSale.sol @@ -0,0 +1,737 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +// pragma abicoder v2; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/PullPaymentUpgradeable.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import "./Sale.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import "../../config/INetworkConfig.sol"; +import "../../utilities/AccessVerifier.sol"; +import { IFeeLevelJudge } from "../../claim/IFeeLevelJudge.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +/** +Allow qualified users to participate in a sale according to sale rules. + +Management +- the address that deploys the sale is the sale owner +- owners may change some sale parameters (e.g. start and end times) +- sale proceeds are sent to the sale recipient + +Qualification +- (deprecated) public sale: anyone can participate +- private sale: only users who have signatures issued by the access authority + +Sale Rules +- timing: purchases can only be made + - after the sale opens + - after the per-account random queue time has elapsed + - before the sale ends +- purchase quantity: quantity is limited by + - per-address limit + - total sale limit +- payment method: participants can pay using either + - the native token on the network (e.g. ETH) + - a single ERC-20 token (e.g. USDC) +- number of purchases: there is no limit to the number of compliant purchases a user may make + +Token Distribution +- this contract does not distribute any purchased tokens + +Metrics +- purchase count: number of purchases made in this sale +- user count: number of unique addresses that participated in this sale +- total bought: value of purchases denominated in a base currency (e.g. USD) as an integer (to get the float value, divide by oracle decimals) +- bought per user: value of a user's purchases denominated in a base currency (e.g. USD) + +total bought and bought per user metrics are inclusive of any fee charged (if a fee is charged, the sale recipient will receive less than the total spend) +*/ + +// Sale can only be updated post-initialization by the contract owner! +struct Config { + // the address that will receive sale proceeds (tokens and native) minus any fees sent to the fee recipient + address payable recipient; + // (deprecated) the merkle root used for proving access + bytes32 merkleRoot; + // max that can be spent in the sale in the base currency + uint256 saleMaximum; + // max that can be spent per user in the base currency + uint256 userMaximum; + // minimum that can be bought in a specific purchase + uint256 purchaseMinimum; + // the time at which the sale starts (users will have an additional random delay if maxQueueTime is set) + uint256 startTime; + // the time at which the sale will end, regardless of tokens raised + uint256 endTime; + // what is the maximum length of time a user could wait in the queue after the sale starts? + uint256 maxQueueTime; + // a link to off-chain information about this sale + string URI; +} + +// Metrics are only updated by the buyWithToken() and buyWithNative() functions +struct Metrics { + // number of purchases + uint256 purchaseCount; + // number of buyers + uint256 buyerCount; + // amount bought denominated in a base currency + uint256 purchaseTotal; + // amount bought for each user denominated in a base currency + mapping(address => uint256) buyerTotal; +} + +struct PaymentTokenInfo { + IOracleOrL2OracleWithSequencerCheck oracle; + uint256 heartbeat; + uint8 decimals; +} + +contract FlatPriceSale_v_4_0 is Sale, PullPaymentUpgradeable, AccessVerifier { + using Address for address payable; + using SafeERC20Upgradeable for IERC20Upgradeable; + + event ImplementationConstructor(INetworkConfig networkConfig); + event Update(Config config); + event Initialize( + Config config, + string baseCurrency, + IOracleOrL2OracleWithSequencerCheck nativeOracle, + bool nativePaymentsEnabled + ); + event SetPaymentTokenInfo( + IERC20Upgradeable token, + PaymentTokenInfo paymentTokenInfo + ); + event SweepToken(address indexed token, uint256 amount); + event SweepNative(uint256 amount); + event RegisterDistributor(address distributor); + + // All supported chains must use 18 decimals (e.g. 1e18 wei / eth) + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + // flag for additional merkle root data + uint8 internal constant PER_USER_PURCHASE_LIMIT = 1; + uint8 internal constant PER_USER_END_TIME = 2; + + /** + Variables set by implementation contract constructor (immutable) + */ + + // denominator used to determine size of fee bips + uint256 constant fractionDenominator = 10000; + + // an optional address where buyers can receive distributed tokens + address distributor; + + /** + Variables set during initialization of clone contracts ("immutable" on each instance) + */ + + // the base currency being used, e.g. 'USD' + string public baseCurrency; + + string public constant VERSION = "4.0"; + + // / price, e.g. ETH/USD price + IOracleOrL2OracleWithSequencerCheck public nativeTokenPriceOracle; + + // heartbeat value for oracle + uint256 public nativeTokenPriceOracleHeartbeat; + + // whether native payments are enabled (set during intialization) + bool nativePaymentsEnabled; + + // / price oracles, eg USDC address => ETH/USDC price + mapping(IERC20Upgradeable => PaymentTokenInfo) public paymentTokens; + + // owner can update these + Config public config; + + // derived from payments + Metrics public metrics; + + // reasonably random value: xor of merkle root and blockhash for transaction setting merkle root + uint160 internal randomValue; + + INetworkConfig public immutable networkConfig; + + // All clones will share the information in the implementation constructor + constructor(address _networkConfig) { + networkConfig = INetworkConfig(_networkConfig); + + emit ImplementationConstructor(networkConfig); + } + + /** + Replacement for constructor for clones of the implementation contract + Important: anyone can call the initialize function! + */ + function initialize( + address _owner, + Config calldata _config, + string calldata _baseCurrency, + bool _nativePaymentsEnabled, + IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, + uint256 _nativeTokenPriceOracleHeartbeat, + IERC20Upgradeable[] calldata tokens, + IOracleOrL2OracleWithSequencerCheck[] calldata oracles, + uint256[] calldata oracleHeartbeats, + uint8[] calldata decimals + ) public initializer validUpdate(_config) { + // initialize the PullPayment escrow contract + __PullPayment_init(); + + __ReentrancyGuard_init(); + + // validate the new sale + require(tokens.length == oracles.length, "token and oracle lengths !="); + require( + tokens.length == decimals.length, + "token and decimals lengths !=" + ); + require( + address(_nativeTokenPriceOracle) != address(0), + "native oracle == 0" + ); + + // save the new sale + config = _config; + + // save payment config + baseCurrency = _baseCurrency; + nativeTokenPriceOracle = _nativeTokenPriceOracle; + nativeTokenPriceOracleHeartbeat = _nativeTokenPriceOracleHeartbeat; + nativePaymentsEnabled = _nativePaymentsEnabled; + emit Initialize( + config, + baseCurrency, + nativeTokenPriceOracle, + _nativePaymentsEnabled + ); + + for (uint256 i = 0; i < tokens.length; i++) { + // double check that tokens and oracles are real addresses + require(address(tokens[i]) != address(0), "payment token == 0"); + require(address(oracles[i]) != address(0), "token oracle == 0"); + // save the payment token info + paymentTokens[tokens[i]] = PaymentTokenInfo({ + oracle: oracles[i], + heartbeat: oracleHeartbeats[i], + decimals: decimals[i] + }); + + emit SetPaymentTokenInfo(tokens[i], paymentTokens[tokens[i]]); + } + + // Set the random value for the fair queue time + randomValue = generatePseudorandomValue(); + + // transfer ownership to the user initializing the sale + _transferOwnership(_owner); + } + + /** + Check that the user can currently participate in the sale based on the access signature + */ + modifier canAccessSale( + uint256 userLimit, + uint64 expiresAt, + bytes memory signature + ) { + // make sure the buyer is an EOA + require((_msgSender() == tx.origin), "Must buy with an EOA"); + + verifyAccessSignature( + networkConfig.getAccessAuthorityAddress(), + _msgSender(), + userLimit, + expiresAt, + signature + ); + + // Require the sale to be open + require(block.timestamp > config.startTime, "sale has not started yet"); + require(block.timestamp < config.endTime, "sale has ended"); + require( + metrics.purchaseTotal < config.saleMaximum, + "sale buy limit reached" + ); + + // Reduce congestion by randomly assigning each user a delay time in a virtual queue based on comparing their address and a random value + // if config.maxQueueTime == 0 the delay is 0 + require( + block.timestamp - config.startTime > getFairQueueTime(_msgSender()), + "not your turn yet" + ); + _; + } + + /** + Check that the new sale is a valid update + - If the config already exists, it must not be over (cannot edit sale after it concludes) + - Sale start, end, and max queue times must be consistent and not too far in the future + */ + modifier validUpdate(Config calldata newConfig) { + // get the existing config + Config memory oldConfig = config; + + /** + - @notice - Block updates after sale is over + - @dev - Since validUpdate is called by initialize(), we can have a new + - sale here, identifiable by default randomValue of 0 + */ + if (randomValue != 0) { + // this is an existing sale: cannot update after it has ended + require( + block.timestamp < oldConfig.endTime, + "sale is over: cannot upate" + ); + if (block.timestamp > oldConfig.startTime) { + // the sale has already started, some things should not be edited + require( + oldConfig.saleMaximum == newConfig.saleMaximum, + "editing saleMaximum after sale start" + ); + } + } + + // the total sale limit must be at least as large as the per-user limit + + // all required values must be present and reasonable + // check if the caller accidentally entered a value in milliseconds instead of seconds + require( + newConfig.startTime <= 4102444800, + "start > 4102444800 (Jan 1 2100)" + ); + require( + newConfig.endTime <= 4102444800, + "end > 4102444800 (Jan 1 2100)" + ); + require( + newConfig.maxQueueTime <= 604800, + "max queue time > 604800 (1 week)" + ); + require(newConfig.recipient != address(0), "recipient == address(0)"); + + // sale, user, and purchase limits must be compatible + require(newConfig.saleMaximum > 0, "saleMaximum == 0"); + require(newConfig.userMaximum > 0, "userMaximum == 0"); + require( + newConfig.userMaximum <= newConfig.saleMaximum, + "userMaximum > saleMaximum" + ); + require( + newConfig.purchaseMinimum <= newConfig.userMaximum, + "purchaseMinimum > userMaximum" + ); + + // new sale times must be internally consistent + require( + newConfig.startTime + newConfig.maxQueueTime < newConfig.endTime, + "sale must be open for at least maxQueueTime" + ); + + _; + } + + modifier validPaymentToken(IERC20Upgradeable token) { + // check that this token is configured as a payment method + PaymentTokenInfo memory info = paymentTokens[token]; + require(address(info.oracle) != address(0), "invalid payment token"); + + _; + } + + modifier areNativePaymentsEnabled() { + require(nativePaymentsEnabled, "native payments disabled"); + + _; + } + + // Get info on a payment token + function getPaymentToken( + IERC20Upgradeable token + ) external view returns (PaymentTokenInfo memory) { + return paymentTokens[token]; + } + + // TODO: reduce duplication between other contracts + // Get a positive token price from a chainlink oracle + function getOraclePrice( + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256) { + (, int256 _price, , uint256 updatedAt, uint80 answeredInRound) = oracle + .latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require( + updatedAt > block.timestamp - heartbeat, + "stale price due to heartbeat" + ); + + return uint256(_price); + } + + /** + Generate a pseudorandom value + This is not a truly random value: + - miners can alter the block hash + - owners can repeatedly call setMerkleRoot() + - owners can choose when to submit the transaction + */ + function generatePseudorandomValue() public view returns (uint160) { + return uint160(uint256(blockhash(block.number - 1))); + } + + /** + Get the delay in seconds that a specific buyer must wait after the sale begins in order to buy tokens in the sale + + Buyers cannot exploit the fair queue when: + - The sale is private (merkle root != bytes32(0)) + - Each eligible buyer gets exactly one address in the merkle root + + Although miners and sellers can minimize the delay for an arbitrary address, these are not significant threats: + - the economic opportunity to miners is zero or relatively small (only specific addresses can participate in private sales, and a better queue postion does not imply high returns) + - sellers can repeatedly set merkle roots to achieve a favorable queue time for any address, but sellers already control the tokens being sold! + */ + function getFairQueueTime(address buyer) public view returns (uint256) { + if (config.maxQueueTime == 0) { + // there is no delay: all addresses may participate immediately + return 0; + } + + // calculate a distance between the random value and the user's address using the XOR distance metric (c.f. Kademlia) + uint160 distance = uint160(buyer) ^ randomValue; + + // calculate a speed at which the queue is exhausted such that all users complete the queue by sale.maxQueueTime + uint160 distancePerSecond = type(uint160).max / + uint160(config.maxQueueTime); + // return the delay (seconds) + return distance / distancePerSecond; + } + + /** + Convert a token quantity (e.g. USDC or ETH) to a base currency (e.g. USD) with the same number of decimals as the price oracle (e.g. 8) + + Example: given 2 NCT tokens, each worth $1.23, tokensToBaseCurrency should return 246000000 ($2.46) + + Function arguments + - tokenQuantity: 2000000000000000000 + - tokenDecimals: 18 + + NCT/USD chainlink oracle (important! the oracle must be / not /, e.g. ETH/USD, ~$2000 not USD/ETH, ~0.0005) + - baseCurrencyPerToken: 123000000 + - baseCurrencyDecimals: 8 + + Calculation: 2000000000000000000 * 123000000 / 1000000000000000000 + + Returns: 246000000 + */ + // function tokensToBaseCurrency(SafeERC20Upgradeable token, uint256 quantity) public view validPaymentToken(token) returns (uint256) { + // PaymentTokenInfo info = paymentTokens[token]; + // return quantity * getOraclePrice(info.oracle) / (10 ** info.decimals); + // } + // TODO: reduce duplication between other contracts + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return + (tokenQuantity * getOraclePrice(oracle, heartbeat)) / + (10 ** tokenDecimals); + } + + function total() external view override returns (uint256) { + return metrics.purchaseTotal; + } + + function isOver() public view override returns (bool) { + return + config.endTime <= block.timestamp || + metrics.purchaseTotal >= config.saleMaximum; + } + + function isOpen() public view override returns (bool) { + return + config.startTime < block.timestamp && + config.endTime > block.timestamp && + metrics.purchaseTotal < config.saleMaximum; + } + + // return the amount bought by this user in base currency + function buyerTotal(address user) external view override returns (uint256) { + return metrics.buyerTotal[user]; + } + + /** + Records a purchase + Follow the Checks -> Effects -> Interactions pattern + * Checks: CALLER MUST ENSURE BUYER IS PERMITTED TO PARTICIPATE IN THIS SALE: THIS METHOD DOES NOT CHECK WHETHER THE BUYER SHOULD BE ABLE TO ACCESS THE SALE! + * Effects: record the payment + * Interactions: none! + */ + function _execute( + uint256 baseCurrencyQuantity, + uint256 userLimit + ) internal { + require( + baseCurrencyQuantity + metrics.buyerTotal[_msgSender()] <= + userLimit, + "purchase exceeds your limit" + ); + + require( + baseCurrencyQuantity + metrics.purchaseTotal <= config.saleMaximum, + "purchase exceeds sale limit" + ); + + require( + baseCurrencyQuantity >= config.purchaseMinimum, + "purchase under minimum" + ); + + // Effects + metrics.purchaseCount += 1; + if (metrics.buyerTotal[_msgSender()] == 0) { + // if no prior purchases, this is a new buyer + metrics.buyerCount += 1; + } + metrics.purchaseTotal += baseCurrencyQuantity; + metrics.buyerTotal[_msgSender()] += baseCurrencyQuantity; + } + + /** + Settle payment made with payment token + Important: this function has no checks! Only call if the purchase is valid! + */ + function _settlePaymentToken( + uint256 baseCurrencyValue, + IERC20Upgradeable token, + uint256 quantity, + uint256 feeLevel, + uint256 platformFlatRateFeeAmount + ) internal { + uint256 fee = (quantity * feeLevel) / fractionDenominator; + token.safeTransferFrom( + _msgSender(), + networkConfig.getFeeRecipient(), + fee + ); + token.safeTransferFrom(_msgSender(), address(this), quantity - fee); + emit Buy( + _msgSender(), + address(token), + baseCurrencyValue, + quantity, + fee, + platformFlatRateFeeAmount + ); + } + + /** + Settle payment made with native token + Important: this function has no checks! Only call if the purchase is valid! + */ + function _settleNativeToken( + uint256 baseCurrencyValue, + uint256 nativeTokenQuantity, + uint256 feeLevel, + uint256 platformFlatRateFeeAmount + ) internal { + uint256 nativeFee = (nativeTokenQuantity * feeLevel) / + fractionDenominator; + _asyncTransfer(networkConfig.getFeeRecipient(), nativeFee); + _asyncTransfer( + config.recipient, + nativeTokenQuantity - nativeFee - platformFlatRateFeeAmount + ); + + // This contract will hold the native token until claimed by the owner + emit Buy( + _msgSender(), + address(0), + baseCurrencyValue, + nativeTokenQuantity, + nativeFee, + platformFlatRateFeeAmount + ); + } + + /** + Pay with the payment token (e.g. USDC) + */ + function buyWithToken( + IERC20Upgradeable token, + uint256 quantity, + uint256 userLimit, + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) + external + payable + override + canAccessSale(userLimit, expiresAt, signature) + validPaymentToken(token) + nonReentrant + { + uint256 nativeBaseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require( + nativeBaseCurrencyValue >= platformFlatRateFeeAmount, + "fee payment below minimum" + ); + + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * + (10 ** NATIVE_TOKEN_DECIMALS)) / + getOraclePrice( + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + )); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + + // convert to base currency from payment tokens + PaymentTokenInfo memory tokenInfo = paymentTokens[token]; + uint256 baseCurrencyValue = tokensToBaseCurrency( + quantity, + tokenInfo.decimals, + tokenInfo.oracle, + tokenInfo.heartbeat + ); + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge( + networkConfig.getStakingAddress() + ); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + + // Checks and Effects + _execute(baseCurrencyValue + platformFlatRateFeeAmount, userLimit); + // Interactions + _settlePaymentToken( + baseCurrencyValue + platformFlatRateFeeAmount, + token, + quantity, + feeLevel, + platformFlatRateFeeAmount + ); + } + + /** + Pay with the native token (e.g. ETH) + */ + function buyWithNative( + uint256 userLimit, + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) + external + payable + override + canAccessSale(userLimit, expiresAt, signature) + areNativePaymentsEnabled + nonReentrant + { + // convert to base currency from native tokens + // converts msg.value (which is how much ETH was sent by user) to USD + // Example: On September 1st, 2024 + // 0.005 * 2450**10e8 => ~$10 = how much in USD represents + // msg.value + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require( + baseCurrencyValue >= platformFlatRateFeeAmount, + "fee payment below minimum" + ); + + // Example: On September 1st, 2024 + // 1/2450 * 10**18 = how much ETH (in wei) we need to represent + // 1 USD + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * + (10 ** NATIVE_TOKEN_DECIMALS)) / + getOraclePrice( + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + )); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge( + networkConfig.getStakingAddress() + ); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + // Checks and Effects + _execute(baseCurrencyValue, userLimit); + // Interactions + _settleNativeToken( + baseCurrencyValue, + msg.value, + feeLevel, + feeAmountInWei + ); + } + + /** + External management functions (only the owner may update the sale) + */ + function update( + Config calldata _config + ) external validUpdate(_config) onlyOwner { + config = _config; + // updates always reset the random value + randomValue = generatePseudorandomValue(); + emit Update(config); + } + + // Tell users where they can claim tokens + function registerDistributor(address _distributor) external onlyOwner { + require(_distributor != address(0), "Distributor == address(0)"); + distributor = _distributor; + emit RegisterDistributor(distributor); + } + + /** + Public management functions + */ + // Sweep an ERC20 token to the recipient (public function) + function sweepToken(IERC20Upgradeable token) external { + uint256 amount = token.balanceOf(address(this)); + token.safeTransfer(config.recipient, amount); + emit SweepToken(address(token), amount); + } + + // sweep native token to the recipient (public function) + function sweepNative() external { + uint256 amount = address(this).balance; + (bool success, ) = config.recipient.call{ value: amount }(""); + require(success, "Transfer failed."); + emit SweepNative(amount); + } +} diff --git a/packages/hardhat/contracts/sale/v4/FlatPriceSaleFactory.sol b/packages/hardhat/contracts/sale/v4/FlatPriceSaleFactory.sol new file mode 100644 index 00000000..a0a334b8 --- /dev/null +++ b/packages/hardhat/contracts/sale/v4/FlatPriceSaleFactory.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.21; + +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "./FlatPriceSale.sol"; + +contract FlatPriceSaleFactory_v_4_0 is Ownable { + address public implementation; + string public constant VERSION = "4.0"; + + event NewSale( + address indexed implementation, + FlatPriceSale_v_4_0 indexed clone, + Config config, + string baseCurrency, + IOracleOrL2OracleWithSequencerCheck nativeOracle, + uint256 nativeOracleHeartbeat, + bool nativePaymentsEnabled + ); + + constructor(address _implementation) { + implementation = _implementation; + } + + function upgradeFutureSales(address _implementation) external onlyOwner { + implementation = _implementation; + } + + function newSale( + address _owner, + Config calldata _config, + string calldata _baseCurrency, + bool _nativePaymentsEnabled, + IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, + uint256 _nativeTokenPriceOracleHeartbeat, + IERC20Upgradeable[] calldata tokens, + IOracleOrL2OracleWithSequencerCheck[] calldata oracles, + uint256[] calldata oracleHeartbeats, + uint8[] calldata decimals + ) external returns (FlatPriceSale_v_4_0 sale) { + sale = FlatPriceSale_v_4_0(Clones.clone(address(implementation))); + + emit NewSale( + implementation, + sale, + _config, + _baseCurrency, + _nativeTokenPriceOracle, + _nativeTokenPriceOracleHeartbeat, + _nativePaymentsEnabled + ); + + sale.initialize( + _owner, + _config, + _baseCurrency, + _nativePaymentsEnabled, + _nativeTokenPriceOracle, + _nativeTokenPriceOracleHeartbeat, + tokens, + oracles, + oracleHeartbeats, + decimals + ); + } +} diff --git a/packages/hardhat/contracts/sale/v4/Sale.sol b/packages/hardhat/contracts/sale/v4/Sale.sol new file mode 100644 index 00000000..58c9d587 --- /dev/null +++ b/packages/hardhat/contracts/sale/v4/Sale.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol"; + +// Upgradeable contracts are required to use clone() in SaleFactory +abstract contract Sale is ReentrancyGuardUpgradeable, OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + event Buy( + address indexed buyer, + address indexed token, + uint256 baseCurrencyValue, + uint256 tokenValue, + uint256 protocolTokenFee, + uint256 platformTokenFee + ); + + /** + Important: the constructor is only called once on the implementation contract (which is never initialized) + Clones using this implementation cannot use this constructor method. + Thus every clone must use the same fields stored in the constructor (feeBips, feeRecipient) + */ + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function buyWithToken( + IERC20Upgradeable token, + uint256 quantity, + uint256 userLimit, + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) external payable virtual {} + + function buyWithNative( + uint256 userLimit, + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) external payable virtual {} + + function isOpen() public view virtual returns (bool) {} + + function isOver() public view virtual returns (bool) {} + + function buyerTotal(address user) external view virtual returns (uint256) {} + + function total() external view virtual returns (uint256) {} +} diff --git a/packages/hardhat/contracts/sale/v4/Test_FlatPriceSale.sol b/packages/hardhat/contracts/sale/v4/Test_FlatPriceSale.sol new file mode 100644 index 00000000..66ee3f89 --- /dev/null +++ b/packages/hardhat/contracts/sale/v4/Test_FlatPriceSale.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +// pragma abicoder v2; + +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import "../../config/INetworkConfig.sol"; +import "../../utilities/AccessVerifier.sol"; +import "@openzeppelin/contracts-upgradeable/security/PullPaymentUpgradeable.sol"; + +contract Test_FlatPriceSale { + using Address for address payable; + + string public constant VERSION = "0.0.1"; + + event OutputOne( + address accessAuthorityAddress, + address sender, + uint256 userLimit, + uint64 expiresAt, + bytes signature, + address contractAddress, + bytes message, + bytes32 hash, + address signer + ); + + /** + Pay with the native token (e.g. ETH). + */ + function buyWithNative( + uint256 userLimit, + uint64 expiresAt, + bytes memory signature // Remove 'view' modifier as we're now emitting an event + ) external { + INetworkConfig networkConfig = INetworkConfig( + 0xe6d4e4d7741e556B2b6123973318B01e5d202831 + ); + bytes memory message = abi.encodePacked( + address(this), + msg.sender, + userLimit, + expiresAt + ); + bytes32 hash = keccak256(message); + bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(hash); + address signer = ECDSA.recover(ethSignedMessageHash, signature); + + emit OutputOne( + networkConfig.getAccessAuthorityAddress(), + msg.sender, + userLimit, + expiresAt, + signature, + address(this), + message, + hash, + signer + ); + } +} diff --git a/packages/hardhat/contracts/utilities/AccessVerifier.sol b/packages/hardhat/contracts/utilities/AccessVerifier.sol new file mode 100644 index 00000000..32cc7a4d --- /dev/null +++ b/packages/hardhat/contracts/utilities/AccessVerifier.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.0.0 +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +contract AccessVerifier { + function verifyAccessSignature( + address accessAuthorityAddress, + address member, + uint256 userLimit, + uint64 expires_at, + bytes memory signature + ) internal view { + bytes memory message = abi.encodePacked( + address(this), + member, + userLimit, + expires_at + ); + bytes32 hash = keccak256(message); + bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(hash); + address signer = ECDSA.recover(ethSignedMessageHash, signature); + + // TODO: use custom error objects with a revert statement + require(signer == accessAuthorityAddress, "access signature invalid"); + require(block.timestamp < expires_at, "access signature expired"); + } +} diff --git a/packages/hardhat/hardhat.config.ts b/packages/hardhat/hardhat.config.ts index bef24a8f..e953d951 100644 --- a/packages/hardhat/hardhat.config.ts +++ b/packages/hardhat/hardhat.config.ts @@ -7,10 +7,11 @@ import "hardhat-jest"; // Typescript // Add the following variables to the configuration variables. const ALCHEMY_API_KEY = vars.get("ALCHEMY_API_KEY"); const EVM_PRIVATE_KEY_1 = vars.get("EVM_PRIVATE_KEY_1"); -const EVM_PRIVATE_KEY_2 = vars.get("EVM_PRIVATE_KEY_2"); +// const EVM_PRIVATE_KEY_2 = vars.get("EVM_PRIVATE_KEY_2"); const ETHERSCAN_API_KEY = vars.get("ETHERSCAN_API_KEY"); const BASESCAN_API_KEY = vars.get("BASESCAN_API_KEY"); const COREDAO_BLOCK_EXPLORER_API_KEY = vars.get("COREDAO_BLOCK_EXPLORER_API_KEY"); +const BSCSCAN_API_KEY = vars.get("BSCSCAN_API_KEY"); const SCROLL_API_KEY = vars.get("SCROLL_API_KEY"); const config: HardhatUserConfig = { @@ -44,6 +45,14 @@ const config: HardhatUserConfig = { url: `https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}`, accounts: [EVM_PRIVATE_KEY_1], }, + bsc: { + url: `https://bnb-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}`, + accounts: [EVM_PRIVATE_KEY_1], + }, + bscTestnet: { + url: `https://bnb-testnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}`, + accounts: [EVM_PRIVATE_KEY_1], + }, base: { url: `https://base-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}`, accounts: [EVM_PRIVATE_KEY_1], @@ -91,6 +100,22 @@ const config: HardhatUserConfig = { browserURL: "https://etherscan.io", }, }, + { + network: "bsc", + chainId: 56, + urls: { + apiURL: "https://api.bscscan.com/api", + browserURL: "https://bscscan.com/", + }, + }, + { + network: "bscTestnet", + chainId: 97, + urls: { + apiURL: "https://api-testnet.bscscan.com/api", + browserURL: "https://testnet.bscscan.com/", + }, + }, { network: "scroll", chainId: 534352, @@ -116,6 +141,8 @@ const config: HardhatUserConfig = { baseSepolia: BASESCAN_API_KEY, scrollSepolia: SCROLL_API_KEY, coredao: COREDAO_BLOCK_EXPLORER_API_KEY, + bsc: BSCSCAN_API_KEY, + bscTestnet: BSCSCAN_API_KEY, }, }, }; diff --git a/packages/hardhat/ignition/modules/Deploy_TestFPS.ts b/packages/hardhat/ignition/modules/Deploy_TestFPS.ts new file mode 100644 index 00000000..312be1bb --- /dev/null +++ b/packages/hardhat/ignition/modules/Deploy_TestFPS.ts @@ -0,0 +1,9 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +const DeployTestFPSModule = buildModule("DeployTestFlatPriceSaleModule", m => { + const testFPS = m.contract("Test_FlatPriceSale", []); + + return { testFPS }; +}); + +export default DeployTestFPSModule; diff --git a/packages/hardhat/ignition/modules/Execute_BuyWithNative.ts b/packages/hardhat/ignition/modules/Execute_BuyWithNative.ts index d27e2c42..a3da8626 100644 --- a/packages/hardhat/ignition/modules/Execute_BuyWithNative.ts +++ b/packages/hardhat/ignition/modules/Execute_BuyWithNative.ts @@ -1,49 +1,73 @@ import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; import { ethers } from "ethers"; -export default buildModule("ExecuteNewSaleModule", m => { - // const deployer = m.getAccount(0); - // const recipient = m.getAccount(1); +export default buildModule("ExecuteBuyWithNativeModule", m => { + // UPDATE VARIABLES + const currentNetwork = "baseSepolia"; + const currentFPSVersion: string = "v4"; + const currentArgsVersion: keyof typeof argsDatabase = "v4"; - /** ETHEREUM MAINNET */ - // const FlatPriceSaleFactoryAddress = "0xaffd5144511fdb139e06733a7413c95a80a9c2ce"; + const addressesDatabase = { + mainnet: { + flatPriceSaleAddress: {}, + }, + sepolia: { + flatPriceSaleAddress: { + "v2.1": "0xaffd5144511fdb139e06733a7413c95a80a9c2ce", + }, + }, + baseSepolia: { + flatPriceSaleAddress: { + v2: "0x0", + "v2.1": "0xC36BA12Ef4dFD202B67CF0609dF39E5FFF693781", + v3: "0x0", + v4: "0x6211ef4F530C86a033A797336c8B88564b6Fc711", + }, + }, + }; - /** BASE SEPOLIA */ - const baseSepolia_FlatPriceSaleAddress_v2 = "0xC36BA12Ef4dFD202B67CF0609dF39E5FFF693781"; - const baseSepolia_FlatPriceSaleAddress_v3 = "0x51bEF78ce0f48b816F065A48B3966e3cbEaA1045"; - - /** - * UPDATE VARIABLES - */ - // const networkChosen = "baseSepolia"; - const versionChosen: string = "v3"; - /** - * - */ - const argsForV2 = ["0x", ["0x396a80388a635517527f4be13d6f750c956dd2c864aeb6389d22ddf2323a1029"]]; - - const argsForV3 = [ - "0x", - ["0x396a80388a635517527f4be13d6f750c956dd2c864aeb6389d22ddf2323a1029"], - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - 100000000, - ]; + const argsDatabase = { + // args for FPS v2 + v2: ["0x", ["0x396a80388a635517527f4be13d6f750c956dd2c864aeb6389d22ddf2323a1029"]], + // args for FPS v3 + v3: [ + "0x", + ["0x396a80388a635517527f4be13d6f750c956dd2c864aeb6389d22ddf2323a1029"], + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + 100000000, + ], + // args for FPS v4 + v4: [ + 100, + 1728950399, + "0x7b05ff4c9c69e35c6639fa82f7b849622a56d86874c6b79cf0bba9b62467faca478f0c05ad822583249abfeb2129561a40610e558ccc0fa043ef0814b093f2431b", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + 100000000, + ], + }; let flatPriceSale; - let args; - if (versionChosen == "v2") { - flatPriceSale = m.contractAt("FlatPriceSale_v_2_1", baseSepolia_FlatPriceSaleAddress_v2); - args = argsForV2; - } else if (versionChosen == "v3") { - flatPriceSale = m.contractAt("FlatPriceSale_v_3", baseSepolia_FlatPriceSaleAddress_v3); - args = argsForV3; + const args = argsDatabase[currentArgsVersion]; + if (currentFPSVersion == "v2") { + flatPriceSale = m.contractAt( + "FlatPriceSale_v_2_1", + addressesDatabase[currentNetwork].flatPriceSaleAddress[currentFPSVersion], + ); + } else if (currentFPSVersion == "v3") { + flatPriceSale = m.contractAt( + "FlatPriceSale_v_3", + addressesDatabase[currentNetwork].flatPriceSaleAddress[currentFPSVersion], + ); + } else if (currentFPSVersion == "v4") { + flatPriceSale = m.contractAt( + "FlatPriceSale_v_4_0", + addressesDatabase[currentNetwork].flatPriceSaleAddress[currentFPSVersion], + ); } else { - throw new Error(`Invalid version: ${versionChosen}`); + throw new Error(`Invalid version: ${currentFPSVersion}`); } - // Add this line to specify the amount of ETH to send - const ethToSend = ethers.parseEther("0.005"); // Adjust this value as needed - + const ethToSend = ethers.parseEther("0.005"); m.call(flatPriceSale, "buyWithNative", args, { value: ethToSend }); return {}; diff --git a/packages/hardhat/ignition/modules/Execute_NewSale.ts b/packages/hardhat/ignition/modules/Execute_NewSale.ts index 6e131536..84e885f5 100644 --- a/packages/hardhat/ignition/modules/Execute_NewSale.ts +++ b/packages/hardhat/ignition/modules/Execute_NewSale.ts @@ -4,137 +4,166 @@ export default buildModule("ExecuteNewSaleModule", m => { const deployer = m.getAccount(0); // const recipient = m.getAccount(1); - /** ETHEREUM MAINNET */ - // const ethOracleAddress = "0x6090149792dAAeE9D1D568c9f9a6F6B46AA29eFD"; - // const usdcAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; - // const usdcOracleAddress = "0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6"; - // const FlatPriceSaleFactoryAddress = "0xaffd5144511fdb139e06733a7413c95a80a9c2ce"; - - /** BASE SEPOLIA */ - const baseSepolia_ethOracleAddress = "0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1"; - const baseSepolia_usdcAddress = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; - const baseSepolia_usdcOracleAddress = "0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165"; - const baseSepolia_FlatPriceSaleFactoryAddress_v2 = "0x934b7D8aAf0ab08cf8dbc45839C867038BC73487"; - const baseSepolia_FlatPriceSaleFactoryAddress_v3 = "0xe085d549c972555b0DD37f01869F235A5Cd0B720"; - - /** SCROLL */ - const scroll_ethOracleAddress = "0x6bF14CB0A831078629D993FDeBcB182b21A8774C"; - const scroll_usdcAddress = "0x0"; - const scroll_usdcOracleAddress = "0x0"; - const scroll_FlatPriceSaleFactoryAddress_v2 = "0x0"; - const scroll_FlatPriceSaleFactoryAddress_v3 = "0x8683361FC3D9dAda4a0adcd3383e65D9cE5A459c"; - /** * UPDATE VARIABLES */ - const networkChosen: string = "scroll"; - const versionChosen: string = "v3"; - /** - * - */ + const currentNetwork = "baseSepolia"; + const currentFPSFVersion: string = "v4"; + const currentConfigVersion: keyof typeof configsDatabase = "v1"; + const currentArgsVersion: keyof typeof argsDatabase = "v3"; - const config = { - // recipient of sale proceeds - recipient: deployer, - // merkle root determining sale access - merkleRoot: "0x38e08c198f919ebfa9a16d4e54821b360241e6a43aac9e9845dfcd3f85153433", - // sale maximum ($1,000,000) - note the 8 decimal precision! - saleMaximum: 10000000000, - // user maximum ($1,000) - userMaximum: 2000000000, - // purchase minimum ($1) - purchaseMinimum: 1000000000, - // start time: now - startTime: Math.floor(new Date().getTime() / 1000), - // end time (10 days from now) - endTime: Math.floor(new Date(new Date().getTime() + 10 * 24 * 3600 * 1000).getTime() / 1000), - // max fair queue time 1 hour - maxQueueTime: 0, - // information about the sale - URI: "ipfs://QmZPtoVLFC8HBzEcJ6hoUsXuaQGF5bdQPr7LeQcSFkNMbA", + const addressesDatabase = { + mainnet: { + ethOracleAddress: "0x6090149792dAAeE9D1D568c9f9a6F6B46AA29eFD", + usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + usdcOracleAddress: "0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6", + flatPriceSaleFactoryAddress: {}, + }, + sepolia: { + ethOracleAddress: "0x694AA1769357215DE4FAC081bf1f309aDC325306", + usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + usdcOracleAddress: "0xA2F78ab2355fe2f984D808B5CeE7FD0A93D5270E", + flatPriceSaleFactoryAddress: { + v2: "", + "v2.1": "0xaffd5144511fdb139e06733a7413c95a80a9c2ce", + v3: "", + v4: "", + }, + }, + baseSepolia: { + ethOracleAddress: "0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1", + usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + usdcOracleAddress: "0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165", + flatPriceSaleFactoryAddress: { + v2: "", + "v2.1": "0x934b7D8aAf0ab08cf8dbc45839C867038BC73487", + v3: "", + unknown: "0xe085d549c972555b0DD37f01869F235A5Cd0B720", + v4: "0x4Be7467d6011CFB00a4b4cbEF69F03536Ee5f76F", + }, + }, + scroll: { + ethOracleAddress: "0x6bF14CB0A831078629D993FDeBcB182b21A8774C", + usdcAddress: "0x0", + usdcOracleAddress: "0x0", + flatPriceSaleFactoryAddress: { + v2: "0x0", + "v2.1": "0x0", + v3: "0x8683361FC3D9dAda4a0adcd3383e65D9cE5A459c", + unknown: "0x0", + v4: "0x0", + }, + }, }; - const argsForV2 = [ - // the owner of the new sale (can later modify the sale) - deployer, - // the sale configuration - config, - // base currency - "USD", - // native payments enabled - true, - // native price oracle - baseSepolia_ethOracleAddress, - // payment tokens - [baseSepolia_usdcAddress], - // payment token price oracles - [baseSepolia_usdcOracleAddress], - // payment token decimals - [6], - ]; - - const argsForV3 = [ - // the owner of the new sale (can later modify the sale) - deployer, - // the sale configuration - config, - // base currency - "USD", - // native payments enabled - true, - // native price oracle - baseSepolia_ethOracleAddress, - // native price oracle heartbeat - 360000, - // payment tokens - [baseSepolia_usdcAddress], - // payment token price oracles - [baseSepolia_usdcOracleAddress], - // payment token price oracle heartbeats - [360000], - // payment token decimals - [6], - ]; + const configsDatabase = { + v1: { + // recipient of sale proceeds + recipient: deployer, + // merkle root determining sale access + merkleRoot: "0x38e08c198f919ebfa9a16d4e54821b360241e6a43aac9e9845dfcd3f85153433", + // sale maximum ($1,000,000) - note the 8 decimal precision! + saleMaximum: 10000000000, + // user maximum ($1,000) + userMaximum: 2000000000, + // purchase minimum ($1) + purchaseMinimum: 1000000000, + // start time: now + startTime: Math.floor(new Date().getTime() / 1000), + // end time (10 days from now) + endTime: Math.floor(new Date(new Date().getTime() + 10 * 24 * 3600 * 1000).getTime() / 1000), + // max fair queue time 1 hour + maxQueueTime: 0, + // information about the sale + URI: "ipfs://QmZPtoVLFC8HBzEcJ6hoUsXuaQGF5bdQPr7LeQcSFkNMbA", + }, + }; - const argsForV3_scroll = [ - // the owner of the new sale (can later modify the sale) - deployer, - // the sale configuration - config, - // base currency - "USD", - // native payments enabled - true, - // native price oracle - scroll_ethOracleAddress, - // native price oracle heartbeat - 360000, - // payment tokens - [], - // payment token price oracles - [], - // payment token price oracle heartbeats - [], - // payment token decimals - [], - ]; + const argsDatabase = { + v2: [ + // the owner of the new sale (can later modify the sale) + deployer, + // the sale configuration + configsDatabase[currentConfigVersion], + // base currency + "USD", + // native payments enabled + true, + // native price oracle + addressesDatabase[currentNetwork].ethOracleAddress, + // payment tokens + [addressesDatabase[currentNetwork].usdcAddress], + // payment token price oracles + [addressesDatabase[currentNetwork].usdcOracleAddress], + // payment token decimals + [6], + ], + v3: [ + // the owner of the new sale (can later modify the sale) + deployer, + // the sale configuration + configsDatabase[currentConfigVersion], + // base currency + "USD", + // native payments enabled + true, + // native price oracle + addressesDatabase[currentNetwork].ethOracleAddress, + // native price oracle heartbeat + 36000, + // payment tokens + [addressesDatabase[currentNetwork].usdcAddress], + // payment token price oracles + [addressesDatabase[currentNetwork].usdcOracleAddress], + // payment token price oracle heartbeats + [36000], + // payment token decimals + [6], + ], + // Used for Scroll + v3NoPaymentMethods: [ + // the owner of the new sale (can later modify the sale) + deployer, + // the sale configuration + configsDatabase[currentConfigVersion], + // base currency + "USD", + // native payments enabled + true, + // native price oracle + addressesDatabase[currentNetwork].ethOracleAddress, + // native price oracle heartbeat + 360000, + // payment tokens + [], + // payment token price oracles + [], + // payment token price oracle heartbeats + [], + // payment token decimals + [], + ], + }; let flatPriceSaleFactory; - let args; - if (versionChosen == "v2") { - flatPriceSaleFactory = m.contractAt("FlatPriceSaleFactory_v_2_1", baseSepolia_FlatPriceSaleFactoryAddress_v2); - args = argsForV2; - } else if (versionChosen == "v3") { - if (networkChosen == "baseSepolia") { - flatPriceSaleFactory = m.contractAt("FlatPriceSaleFactory_v_3", baseSepolia_FlatPriceSaleFactoryAddress_v3); - args = argsForV3; - } else { - // (networkChosen == "scroll") - flatPriceSaleFactory = m.contractAt("FlatPriceSaleFactory_v_3", scroll_FlatPriceSaleFactoryAddress_v3); - args = argsForV3_scroll; - } + const args = argsDatabase[currentArgsVersion]; + if (currentFPSFVersion == "v2") { + flatPriceSaleFactory = m.contractAt( + "FlatPriceSaleFactory_v_2_1", + addressesDatabase[currentNetwork].flatPriceSaleFactoryAddress.v2, + ); + } else if (currentFPSFVersion == "v3") { + flatPriceSaleFactory = m.contractAt( + "FlatPriceSaleFactory_v_3", + addressesDatabase[currentNetwork].flatPriceSaleFactoryAddress.v3, + ); + } else if (currentFPSFVersion == "v4") { + flatPriceSaleFactory = m.contractAt( + "FlatPriceSaleFactory_v_4_0", + addressesDatabase[currentNetwork].flatPriceSaleFactoryAddress.v4, + ); } else { - throw new Error(`Invalid version: ${versionChosen}`); + throw new Error(`Invalid version: ${currentFPSFVersion}`); } m.call(flatPriceSaleFactory, "newSale", args); diff --git a/packages/hardhat/ignition/modules/FlatPriceSaleFactoryV4Module.ts b/packages/hardhat/ignition/modules/FlatPriceSaleFactoryV4Module.ts new file mode 100644 index 00000000..df43fa82 --- /dev/null +++ b/packages/hardhat/ignition/modules/FlatPriceSaleFactoryV4Module.ts @@ -0,0 +1,11 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; +import NetworkConfigModule from "./NetworkConfigModule"; + +export default buildModule("FlatPriceSaleFactoryV4Module", m => { + const { networkConfig } = m.useModule(NetworkConfigModule); + + const flatPriceSale_v_4_0 = m.contract("FlatPriceSale_v_4_0", [networkConfig]); + const flatPriceSaleFactory_v_4_0 = m.contract("FlatPriceSaleFactory_v_4_0", [flatPriceSale_v_4_0]); + + return { networkConfig, flatPriceSale_v_4_0, flatPriceSaleFactory_v_4_0 }; +}); diff --git a/packages/hardhat/ignition/modules/InitializeNetworkConfigModule.ts b/packages/hardhat/ignition/modules/InitializeNetworkConfigModule.ts index 77ceffbd..6aebbf52 100644 --- a/packages/hardhat/ignition/modules/InitializeNetworkConfigModule.ts +++ b/packages/hardhat/ignition/modules/InitializeNetworkConfigModule.ts @@ -12,6 +12,7 @@ export default buildModule("InitializeNetworkConfigModule", m => { feeLevelJudgeStub, m.getParameter("NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_ADDRESS"), m.getParameter("NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT"), + m.getParameter("NETWORK_CONFIG_ACCESS_AUTHORITY_ADDRESS"), ]); return { networkConfig }; diff --git a/packages/hardhat/ignition/modules/Oct6SuiteModule.ts b/packages/hardhat/ignition/modules/Oct6SuiteModule.ts new file mode 100644 index 00000000..558e69d4 --- /dev/null +++ b/packages/hardhat/ignition/modules/Oct6SuiteModule.ts @@ -0,0 +1,26 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +// import SuiteDistributorFactoryV4Module from "./SuiteDistributorFactoryV4Module"; +import FlatPriceSaleFactoryV4Module from "./FlatPriceSaleFactoryV4Module"; +import InitializeNetworkConfigModule from "./InitializeNetworkConfigModule"; + +/* + Update these variables in the ../parameters/ folder prior to running this module: + - NETWORK_CONFIG_PROXY_ADMIN + - NETWORK_CONFIG_FEE_RECIPIENT + - NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_ADDRESS + - NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT + - NETWORK_CONFIG_ACCESS_AUTHORITY_ADDRESS +*/ + +export default buildModule("Oct6SuiteModule", m => { + // const suiteDistributorFactoryModule = m.useModule(SuiteDistributorFactoryV4Module); + const flatPriceSaleFactoryV4Module = m.useModule(FlatPriceSaleFactoryV4Module); + const initializeNetworkConfigModule = m.useModule(InitializeNetworkConfigModule); + + return { + // ...suiteDistributorFactoryModule, + ...flatPriceSaleFactoryV4Module, + ...initializeNetworkConfigModule, + }; +}); diff --git a/packages/hardhat/ignition/modules/SuiteDistributorFactoryV5Module.ts b/packages/hardhat/ignition/modules/SuiteDistributorFactoryV5Module.ts new file mode 100644 index 00000000..8a35e103 --- /dev/null +++ b/packages/hardhat/ignition/modules/SuiteDistributorFactoryV5Module.ts @@ -0,0 +1,21 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +export default buildModule("SuiteDistributorFactoryV5Module", m => { + const continuousVestingMerkleDistributor_v_5_0 = m.contract("ContinuousVestingMerkleDistributor_v_5_0", []); + const trancheVestingMerkleDistributor_v_5_0 = m.contract("TrancheVestingMerkleDistributor_v_5_0", []); + + const continuousVestingMerkleDistributorFactory_v_5_0 = m.contract( + "ContinuousVestingMerkleDistributorFactory_v_5_0", + [continuousVestingMerkleDistributor_v_5_0], + ); + const trancheVestingMerkleDistributorFactory_v_5_0 = m.contract("TrancheVestingMerkleDistributorFactory_v_5_0", [ + trancheVestingMerkleDistributor_v_5_0, + ]); + + return { + continuousVestingMerkleDistributor_v_5_0, + trancheVestingMerkleDistributor_v_5_0, + continuousVestingMerkleDistributorFactory_v_5_0, + trancheVestingMerkleDistributorFactory_v_5_0, + }; +}); diff --git a/packages/hardhat/ignition/parameters/baseSepolia_parameters.json b/packages/hardhat/ignition/parameters/baseSepolia_parameters.json index 339063b1..bfd05315 100644 --- a/packages/hardhat/ignition/parameters/baseSepolia_parameters.json +++ b/packages/hardhat/ignition/parameters/baseSepolia_parameters.json @@ -5,6 +5,7 @@ "InitializeNetworkConfigModule": { "NETWORK_CONFIG_FEE_RECIPIENT": "0xfa1b5ce1ae69563d216fc9d8fcf7fef921be02f8", "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_ADDRESS": "0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1", - "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000" + "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000", + "NETWORK_CONFIG_ACCESS_AUTHORITY_ADDRESS": "0x02fDCc7cEFF66FB4871027001DE8A7e89e84baaC" } } diff --git a/packages/hardhat/ignition/parameters/base_parameters.json b/packages/hardhat/ignition/parameters/base_parameters.json index 728d3573..e085ea1c 100644 --- a/packages/hardhat/ignition/parameters/base_parameters.json +++ b/packages/hardhat/ignition/parameters/base_parameters.json @@ -5,6 +5,7 @@ "InitializeNetworkConfigModule": { "NETWORK_CONFIG_FEE_RECIPIENT": "0xfa1b5ce1ae69563d216fc9d8fcf7fef921be02f8", "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_ADDRESS": "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70", - "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000" + "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000", + "NETWORK_CONFIG_ACCESS_AUTHORITY_ADDRESS": "0x02fDCc7cEFF66FB4871027001DE8A7e89e84baaC" } } diff --git a/packages/hardhat/ignition/parameters/mainnet_parameters.json b/packages/hardhat/ignition/parameters/mainnet_parameters.json index 23608aa9..f626749c 100644 --- a/packages/hardhat/ignition/parameters/mainnet_parameters.json +++ b/packages/hardhat/ignition/parameters/mainnet_parameters.json @@ -5,6 +5,7 @@ "InitializeNetworkConfigModule": { "NETWORK_CONFIG_FEE_RECIPIENT": "0x52c263698B5B11AaCAf0f74333DC921B26FFA5b7", "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_ADDRESS": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", - "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000" + "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000", + "NETWORK_CONFIG_ACCESS_AUTHORITY_ADDRESS": "0x02fDCc7cEFF66FB4871027001DE8A7e89e84baaC" } } diff --git a/packages/hardhat/ignition/parameters/scrollSepolia_parameters.json b/packages/hardhat/ignition/parameters/scrollSepolia_parameters.json index 869b5739..18474580 100644 --- a/packages/hardhat/ignition/parameters/scrollSepolia_parameters.json +++ b/packages/hardhat/ignition/parameters/scrollSepolia_parameters.json @@ -5,6 +5,7 @@ "InitializeNetworkConfigModule": { "NETWORK_CONFIG_FEE_RECIPIENT": "0xfa1b5ce1ae69563d216fc9d8fcf7fef921be02f8", "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_ADDRESS": "0x59F1ec1f10bD7eD9B938431086bC1D9e233ECf41", - "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000" + "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000", + "NETWORK_CONFIG_ACCESS_AUTHORITY_ADDRESS": "0x02fDCc7cEFF66FB4871027001DE8A7e89e84baaC" } } diff --git a/packages/hardhat/ignition/parameters/scroll_parameters.json b/packages/hardhat/ignition/parameters/scroll_parameters.json index d1c3fbec..9e1067b5 100644 --- a/packages/hardhat/ignition/parameters/scroll_parameters.json +++ b/packages/hardhat/ignition/parameters/scroll_parameters.json @@ -5,6 +5,7 @@ "InitializeNetworkConfigModule": { "NETWORK_CONFIG_FEE_RECIPIENT": "0xfa1b5ce1ae69563d216fc9d8fcf7fef921be02f8", "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_ADDRESS": "0x6bF14CB0A831078629D993FDeBcB182b21A8774C", - "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000" + "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000", + "NETWORK_CONFIG_ACCESS_AUTHORITY_ADDRESS": "0x02fDCc7cEFF66FB4871027001DE8A7e89e84baaC" } } diff --git a/packages/hardhat/ignition/parameters/sepolia_parameters.json b/packages/hardhat/ignition/parameters/sepolia_parameters.json index 60d4c4af..d681d8db 100644 --- a/packages/hardhat/ignition/parameters/sepolia_parameters.json +++ b/packages/hardhat/ignition/parameters/sepolia_parameters.json @@ -5,6 +5,7 @@ "InitializeNetworkConfigModule": { "NETWORK_CONFIG_FEE_RECIPIENT": "0xfa1b5ce1ae69563d216fc9d8fcf7fef921be02f8", "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_ADDRESS": "0x694AA1769357215DE4FAC081bf1f309aDC325306", - "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000" + "NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT": "36000", + "NETWORK_CONFIG_ACCESS_AUTHORITY_ADDRESS": "0x02fDCc7cEFF66FB4871027001DE8A7e89e84baaC" } } diff --git a/packages/subgraph/config.json b/packages/subgraph/config.json index ebc040b6..903389f9 100644 --- a/packages/subgraph/config.json +++ b/packages/subgraph/config.json @@ -189,6 +189,18 @@ "Commit": "ebe10653145b5ceee86f6810c3e02814a3e92b92" } } + ], + "FlatPriceSale_v_4_0": [ + { + "Address": "0x8a1278fa9B316EB48e8E015e6B7d983F4236c652", + "BlockNumber": "43204851" + } + ], + "FlatPriceSaleFactory_v_4_0": [ + { + "Address": "0xBF38E9dA83DE7a1002d033F14b27d44bD3E2D078", + "BlockNumber": "43204856" + } ] }, "bscTestnet": { @@ -553,6 +565,18 @@ "Address": "0xF7A48Cd5e9789dAA2810c2aa6A89D3464639520A", "BlockNumber": 20772303 } + ], + "FlatPriceSale_v_4_0": [ + { + "Address": "0x08299e0fA3eB142122A8A7973888b0C990B097F8", + "BlockNumber": "20975422" + } + ], + "FlatPriceSaleFactory_v_4_0": [ + { + "Address": "0xEB4605DB7e7B08f8D69697d2D52EC4b7776f346A", + "BlockNumber": "20975427" + } ] }, "matic": { @@ -898,6 +922,18 @@ "Address": "0x95503E5211A46bdA8A7FEA349De09388A04472D9", "BlockNumber": 15842002 } + ], + "FlatPriceSale_v_4_0": [ + { + "Address": "0xa8aE1B587F97D6a6BC1D48C551eA3eD53d885358", + "BlockNumber": "16629801" + } + ], + "FlatPriceSaleFactory_v_4_0": [ + { + "Address": "0x4Be7467d6011CFB00a4b4cbEF69F03536Ee5f76F", + "BlockNumber": "16629808" + } ] }, "base": { @@ -1000,6 +1036,38 @@ } ] }, + "zkSync": { + "SubgraphNetwork": "zksync-era", + "LowestStartBlock": 46937615, + "FlatPriceSale_v_4_0": [ + { + "Address": "0x5D472F0509801Cd295b1636D6Cf38B8643F3C78D", + "BlockNumber": "46937616" + } + ], + "FlatPriceSaleFactory_v_4_0": [ + { + "Address": "0xd14FE6459168bEFACDe8d9ee9305d9827Ab51709", + "BlockNumber": "46937626" + } + ] + }, + "zkSyncSepolia": { + "SubgraphNetwork": "zksync-era-sepolia", + "LowestStartBlock": 3946161, + "FlatPriceSale_v_4_0": [ + { + "Address": "0x80215884BB658b72741ad27Dd9C58414De04f01B", + "BlockNumber": "3946162" + } + ], + "FlatPriceSaleFactory_v_4_0": [ + { + "Address": "0x8465C8ca6d1b8EDF7444d5E4BB9b45e1F35b1D02", + "BlockNumber": "3946164" + } + ] + }, "scrollSepolia": { "SubgraphNetwork": "scroll-sepolia", "LowestStartBlock": 5517393, diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index 8b99cb63..cc07ff26 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -23,6 +23,8 @@ "prepare:polygon-amoy": "NETWORK=polygonAmoy yarn active-config && handlebars active-config.json < src/subgraph.template.yaml > subgraph.yaml", "prepare:optimism": "NETWORK=optimism yarn active-config && handlebars active-config.json < src/subgraph.template.yaml > subgraph.yaml", "prepare:optimism-goerli": "NETWORK=goerliOptimism yarn active-config && handlebars active-config.json < src/subgraph.template.yaml > subgraph.yaml", + "prepare:zksync": "NETWORK=zkSync yarn active-config && handlebars active-config.json < src/subgraph.template.yaml > subgraph.yaml", + "prepare:zksync-sepolia": "NETWORK=zkSyncSepolia yarn active-config && handlebars active-config.json < src/subgraph.template.yaml > subgraph.yaml", "prepare:scroll-sepolia": "NETWORK=scrollSepolia yarn active-config && handlebars active-config.json < src/subgraph.template.yaml > subgraph.yaml", "prepare:scroll": "NETWORK=scroll yarn active-config && handlebars active-config.json < src/subgraph.template.yaml > subgraph.yaml", "auth": "graph auth --product hosted-service ${GRAPH_HOSTED_SERVICE_ACCESS_TOKEN}", @@ -40,9 +42,7 @@ "deploy:avalanche": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-avalanche", "deploy:avalanche-next": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-avalanche-next", "deploy:avalanche-satsuma": "graph deploy sales-avalanche --version-label ${VERSION_LABEL} --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", - "deploy:bsc": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-bsc", - "deploy:bsc-next": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-bsc-next", - "deploy:bsc-satsuma": "graph deploy sales-bsc --version-label ${VERSION_LABEL} --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", + "deploy:bsc-alchemy-satsuma": "graph deploy bsc --version-label v0.0.2 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", "deploy:bsc-testnet": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-bsc-testnet", "deploy:bsc-testnet-next": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-bsc-testnet-next", "deploy:bsc-testnet-satsuma": "graph deploy sales-bsc-testnet --version-label ${VERSION_LABEL} --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", @@ -54,12 +54,12 @@ "deploy:gnosis-satsuma": "graph deploy sales-gnosis --version-label ${VERSION_LABEL} --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", "deploy:goerli": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-goerli", "deploy:sepolia-alchemy-satsuma": "graph deploy sepolia --version-label v0.0.3 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", - "deploy:base-alchemy-satsuma": "graph deploy base --version-label v0.0.3 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", "deploy:base-sepolia-alchemy-satsuma": "graph deploy base-sepolia --version-label v0.0.5 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", - "deploy:goerli-next": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-goerli-next", - "deploy:goerli-satsuma": "graph deploy sales-goerli --version-label ${VERSION_LABEL} --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", + "deploy:base-alchemy-satsuma": "graph deploy base --version-label v0.0.3 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", "deploy:localhost": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 tokensoft/sales-localhost", - "deploy:mainnet-alchemy-satsuma": "graph deploy eth-mainnet --version-label v0.0.4 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", + "deploy:mainnet": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-mainnet", + "deploy:mainnet-next": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-mainnet-next", + "deploy:mainnet-alchemy-satsuma": "graph deploy mainnet --version-label v0.0.2 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", "deploy:matic": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-matic", "deploy:matic-next": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-matic-next", "deploy:matic-satsuma": "graph deploy sales-matic --version-label ${VERSION_LABEL} --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", @@ -73,6 +73,8 @@ "deploy:optimism-goerli": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-optimism-goerli", "deploy:optimism-goerli-next": "yarn auth && graph deploy --product hosted-service --node https://api.thegraph.com/deploy/ tokensoft/sales-optimism-goerli-next", "deploy:optimism-goerli-satsuma": "graph deploy sales-optimism-goerli --version-label ${VERSION_LABEL} --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", + "deploy:zksync-alchemy-satsuma": "graph deploy zksync --version-label v0.0.1 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", + "deploy:zksync-sepolia-alchemy-satsuma": "graph deploy zksync-sepolia --version-label v0.0.3 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", "deploy:scroll-the-graph": "graph deploy --studio scroll-main-1", "deploy:scroll-alchemy-satsuma": "graph deploy scroll --version-label v0.0.3 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --deploy-key ${SATSUMA_DEPLOY_KEY} --ipfs https://ipfs.satsuma.xyz", "deploy:scroll-sepolia-the-graph": "graph deploy --studio scroll-sepolia-1", @@ -86,9 +88,7 @@ "ship:avalanche": "yarn abi-copy && yarn abi-publish && yarn prepare:avalanche && yarn deploy:avalanche", "ship:avalanche-next": "yarn abi-copy && yarn abi-publish && yarn prepare:avalanche && yarn deploy:avalanche-next", "ship:avalanche-satsuma": "yarn abi-copy && yarn abi-publish && yarn prepare:avalanche && yarn deploy:avalanche-satsuma", - "ship:bsc": "yarn abi-copy && yarn abi-publish && yarn prepare:bsc && yarn deploy:bsc", - "ship:bsc-next": "yarn abi-copy && yarn abi-publish && yarn prepare:bsc && yarn deploy:bsc-next", - "ship:bsc-satsuma": "yarn abi-copy && yarn abi-publish && yarn prepare:bsc && yarn deploy:bsc-satsuma", + "ship:bsc-alchemy-satsuma": "yarn abi-copy && yarn prepare:bsc && yarn codegen && yarn deploy:bsc-alchemy-satsuma", "ship:bsc-testnet": "yarn abi-copy && yarn abi-publish && yarn prepare:bsc-testnet && yarn deploy:bsc-testnet", "ship:bsc-testnet-next": "yarn abi-copy && yarn abi-publish && yarn prepare:bsc-testnet && yarn deploy:bsc-testnet-next", "ship:bsc-testnet-satsuma": "yarn abi-copy && yarn abi-publish && yarn prepare:bsc-testnet && yarn deploy:bsc-testnet-satsuma", @@ -99,12 +99,16 @@ "ship:gnosis-next": "yarn abi-copy && yarn abi-publish && yarn prepare:gnosis && yarn deploy:gnosis-next", "ship:gnosis-satsuma": "yarn abi-copy && yarn abi-publish && yarn prepare:gnosis && yarn deploy:gnosis-satsuma", "ship:goerli": "yarn abi-copy && yarn abi-publish && yarn prepare:goerli && yarn deploy:goerli", + "ship:sepolia": "yarn abi-copy && yarn abi-publish && yarn prepare:sepolia && yarn deploy:sepolia", "ship:sepolia-alchemy-satsuma": "yarn abi-copy && yarn prepare:sepolia && yarn codegen && yarn deploy:sepolia-alchemy-satsuma", "ship:base-sepolia-alchemy-satsuma": "yarn abi-copy && yarn prepare:base-sepolia && yarn codegen && yarn deploy:base-sepolia-alchemy-satsuma", + "ship:base": "yarn abi-copy && yarn abi-publish && yarn prepare:base && yarn deploy:base", "ship:base-alchemy-satsuma": "yarn abi-copy && yarn prepare:base && yarn codegen && yarn deploy:base-alchemy-satsuma", "ship:goerli-next": "yarn abi-copy && yarn abi-publish && yarn prepare:goerli && yarn deploy:goerli-next", "ship:goerli-satsuma": "yarn abi-copy && yarn abi-publish && yarn prepare:goerli && yarn deploy:goerli-satsuma", "ship:localhost": "yarn abi-copy && yarn abi-publish --network localhost && yarn deploy:localhost", + "ship:mainnet": "yarn abi-copy && yarn abi-publish && yarn prepare:mainnet && yarn deploy:mainnet", + "ship:mainnet-satsuma": "yarn abi-copy && yarn abi-publish && yarn prepare:mainnet && yarn deploy:mainnet-satsuma", "ship:mainnet-alchemy-satsuma": "yarn abi-copy && yarn prepare:mainnet && yarn codegen && yarn deploy:mainnet-alchemy-satsuma", "ship:matic": "yarn abi-copy && yarn abi-publish && yarn prepare:matic && yarn deploy:matic", "ship:matic-next": "yarn abi-copy && yarn abi-publish && yarn prepare:matic && yarn deploy:matic-next", @@ -119,6 +123,8 @@ "ship:optimism-goerli": "yarn abi-copy && yarn abi-publish && yarn prepare:optimism-goerli && yarn deploy:optimism-goerli", "ship:optimism-goerli-next": "yarn abi-copy && yarn abi-publish && yarn prepare:optimism-goerli && yarn deploy:optimism-goerli-next", "ship:optimism-goerli-satsuma": "yarn abi-copy && yarn abi-publish && yarn prepare:optimism-goerli && yarn deploy:optimism-goerli-satsuma", + "ship:zksync-alchemy-satsuma": "yarn abi-copy && yarn prepare:zksync && yarn codegen && yarn deploy:zksync-alchemy-satsuma", + "ship:zksync-sepolia-alchemy-satsuma": "yarn abi-copy && yarn prepare:zksync-sepolia && yarn codegen && yarn deploy:zksync-sepolia-alchemy-satsuma", "ship:scroll-the-graph": "yarn abi-copy && yarn prepare:scroll && yarn codegen && yarn deploy:scroll-the-graph", "ship:scroll-alchemy-satsuma": "yarn abi-copy && yarn prepare:scroll && yarn codegen && yarn deploy:scroll-alchemy-satsuma", "ship:scroll-sepolia-the-graph": "yarn abi-copy && yarn prepare:scroll-sepolia && yarn codegen && yarn deploy:scroll-sepolia-the-graph", diff --git a/packages/subgraph/src/lib.ts b/packages/subgraph/src/lib.ts index ae7463d4..6cae296c 100644 --- a/packages/subgraph/src/lib.ts +++ b/packages/subgraph/src/lib.ts @@ -1,556 +1,713 @@ -import { Address, ethereum, BigInt, Bytes, log, store } from "@graphprotocol/graph-ts"; +import { + Address, + ethereum, + BigInt, + Bytes, + log, + store +} from "@graphprotocol/graph-ts"; import { IDistributor } from "../generated/templates/Distributor/IDistributor"; -import { Distributor as DistributorTemplate, AdvancedDistributor as AdvancedDistributorTemplate, CrosschainDistributor, TrancheVesting as TrancheVestingTemplate, MerkleSet as MerkleSetTemplate } from '../generated/templates' -import { IERC20Metadata } from '../generated/Registry/IERC20Metadata' -import { ITrancheVesting } from '../generated/Registry/ITrancheVesting' -import { IContinuousVesting } from '../generated/Registry/IContinuousVesting' -import { IPriceTierVesting } from '../generated/Registry/IPriceTierVesting' -import { IMerkleSet } from '../generated/Registry/IMerkleSet' import { - Account, Adjustment, AdvancedDistributor, Claim, ContinuousVesting, DistributionRecord, Distributor, MerkleSet, PaymentMethod, RegisteredAddress, Registry, Sale, SaleImplementation, Tranche, PriceTier, TrancheVesting } from "../generated/schema"; + Distributor as DistributorTemplate, + AdvancedDistributor as AdvancedDistributorTemplate, + CrosschainDistributor, + TrancheVesting as TrancheVestingTemplate, + MerkleSet as MerkleSetTemplate +} from "../generated/templates"; +import { IERC20Metadata } from "../generated/Registry/IERC20Metadata"; +import { ITrancheVesting } from "../generated/Registry/ITrancheVesting"; +import { IContinuousVesting } from "../generated/Registry/IContinuousVesting"; +import { IPriceTierVesting } from "../generated/Registry/IPriceTierVesting"; +import { IMerkleSet } from "../generated/Registry/IMerkleSet"; +import { + Account, + Adjustment, + AdvancedDistributor, + Claim, + ContinuousVesting, + DistributionRecord, + Distributor, + MerkleSet, + PaymentMethod, + RegisteredAddress, + Registry, + Sale, + SaleImplementation, + Tranche, + PriceTier, + TrancheVesting +} from "../generated/schema"; import { AdvancedDistributor as AdvancedDistributorContract } from "../generated/templates/AdvancedDistributor/AdvancedDistributor"; /** * Always gets an Account record from and Address */ -export function getOrCreateAccount(address: Address, block: ethereum.Block): Account { - // Get or create owner entity - const accountId = address.toHexString(); - - let account = Account.load(accountId); - if (account === null) { - account = new Account(accountId); - account.createdAt = block.timestamp; - account.save() - } - return account +export function getOrCreateAccount( + address: Address, + block: ethereum.Block +): Account { + // Get or create owner entity + const accountId = address.toHexString(); + + let account = Account.load(accountId); + if (account === null) { + account = new Account(accountId); + account.createdAt = block.timestamp; + account.save(); + } + return account; } /** * Always gets a Native Payment Method record */ -export function getOrCreateNativePaymentMethod(oracle: Address, block: ethereum.Block): PaymentMethod { - const paymentMethodId = 'native-' + oracle.toHexString(); - let paymentMethod = PaymentMethod.load(paymentMethodId); - if (!paymentMethod) { - paymentMethod = new PaymentMethod(paymentMethodId); - paymentMethod.native = true; - paymentMethod.token = null; - paymentMethod.oracle = oracle; - // all EVMs use 18 decimals for the native token - paymentMethod.decimals = 18; - paymentMethod.symbol = null; - paymentMethod.purchaseTotal = BigInt.fromI32(0); - paymentMethod.purchaseCount = BigInt.fromI32(0); - paymentMethod.save() - } - return paymentMethod +export function getOrCreateNativePaymentMethod( + oracle: Address, + block: ethereum.Block +): PaymentMethod { + const paymentMethodId = "native-" + oracle.toHexString(); + let paymentMethod = PaymentMethod.load(paymentMethodId); + if (!paymentMethod) { + paymentMethod = new PaymentMethod(paymentMethodId); + paymentMethod.native = true; + paymentMethod.token = null; + paymentMethod.oracle = oracle; + // all EVMs use 18 decimals for the native token + paymentMethod.decimals = 18; + paymentMethod.symbol = null; + paymentMethod.purchaseTotal = BigInt.fromI32(0); + paymentMethod.purchaseCount = BigInt.fromI32(0); + paymentMethod.save(); + } + return paymentMethod; } /** * Always gets a Token Payment Method record */ -export function getOrCreateTokenPaymentMethod(token: Address, decimals: i32, oracle: Address | null, block: ethereum.Block): PaymentMethod { - const paymentMethodId = token.toHexString() + '-' + (oracle ? oracle.toHexString() : 'v1.x_priced_at_par'); - let paymentMethod = PaymentMethod.load(paymentMethodId); - if (!paymentMethod) { - paymentMethod = new PaymentMethod(paymentMethodId); - paymentMethod.native = false; - paymentMethod.token = token; - // v1.x contracts have no oracle for the token payment method (assume none) - paymentMethod.oracle = oracle; - paymentMethod.decimals = decimals; - paymentMethod.purchaseTotal = BigInt.fromI32(0); - paymentMethod.purchaseCount = BigInt.fromI32(0); - - // call the token contract get_symbol and set paymentMethod.symbol - const tokenContract = IERC20Metadata.bind(token) - const symbolResult = tokenContract.try_symbol() - if (!symbolResult.reverted) paymentMethod.symbol = symbolResult.value - - paymentMethod.save() - } - return paymentMethod +export function getOrCreateTokenPaymentMethod( + token: Address, + decimals: i32, + oracle: Address | null, + block: ethereum.Block +): PaymentMethod { + const paymentMethodId = + token.toHexString() + + "-" + + (oracle ? oracle.toHexString() : "v1.x_priced_at_par") + + "-" + + decimals.toString(); + let paymentMethod = PaymentMethod.load(paymentMethodId); + if (!paymentMethod) { + paymentMethod = new PaymentMethod(paymentMethodId); + paymentMethod.native = false; + paymentMethod.token = token; + // v1.x contracts have no oracle for the token payment method (assume none) + paymentMethod.oracle = oracle; + paymentMethod.decimals = decimals; + paymentMethod.purchaseTotal = BigInt.fromI32(0); + paymentMethod.purchaseCount = BigInt.fromI32(0); + + // call the token contract get_symbol and set paymentMethod.symbol + const tokenContract = IERC20Metadata.bind(token); + const symbolResult = tokenContract.try_symbol(); + if (!symbolResult.reverted) paymentMethod.symbol = symbolResult.value; + + paymentMethod.save(); + } + return paymentMethod; } /** * Always gets a Distributor */ -export function getOrCreateDistributor(distributorAddress: Address, block: ethereum.Block): Distributor { - const distributorId = distributorAddress.toHexString(); - let distributor = Distributor.load(distributorId); - - if (!distributor) { - DistributorTemplate.create(distributorAddress) - - const distributorContract = IDistributor.bind(distributorAddress); - distributor = new Distributor(distributorId); - - const nameResult = distributorContract.try_NAME(); - if (!nameResult.reverted) { - distributor.name = nameResult.value; - } else distributor.name = 'Unknown'; - - const versionResult = distributorContract.try_VERSION(); - if (!versionResult.reverted) distributor.version = versionResult.value; - else distributor.version = BigInt.fromI32(0); - - const tokenResult = distributorContract.try_token(); - if (!tokenResult.reverted) { - distributor.token = tokenResult.value; - const tokenContract = IERC20Metadata.bind(tokenResult.value) - - const symbolResult = tokenContract.try_symbol() - if (!symbolResult.reverted) distributor.tokenSymbol = symbolResult.value - else distributor.tokenSymbol = 'Unknown' - - const decimalsResult = tokenContract.try_decimals() - if (!decimalsResult.reverted) distributor.tokenDecimals = BigInt.fromI32(decimalsResult.value) - else distributor.tokenDecimals = BigInt.fromI32(18) - } else { - distributor.token = Bytes.fromI32(0); - distributor.tokenSymbol = 'Unknown'; - distributor.tokenDecimals = BigInt.fromI32(18); - } - - const totalResult = distributorContract.try_total(); - if (!totalResult.reverted) { - distributor.total = totalResult.value; - distributor.remaining = totalResult.value; - } else { - distributor.total = BigInt.fromI32(0); - distributor.remaining = BigInt.fromI32(0); - } - - const uriResult = distributorContract.try_uri(); - if (!uriResult.reverted) distributor.uris = [uriResult.value]; - else distributor.uris = ['unknown']; - - const fractionDenominatorResult = distributorContract.try_getFractionDenominator(); - if (fractionDenominatorResult.reverted) { - // default to a fraction denominator of 10000 (fractions are specified in basis points) - distributor.fractionDenominator = BigInt.fromI32(10000); - } else { - distributor.fractionDenominator = fractionDenominatorResult.value; - } - - distributor.claimed = BigInt.fromI32(0); - distributor.createdAt = block.timestamp; - distributor.save(); - } - - return distributor; +export function getOrCreateDistributor( + distributorAddress: Address, + block: ethereum.Block +): Distributor { + const distributorId = distributorAddress.toHexString(); + let distributor = Distributor.load(distributorId); + + if (!distributor) { + DistributorTemplate.create(distributorAddress); + + const distributorContract = IDistributor.bind(distributorAddress); + distributor = new Distributor(distributorId); + + const nameResult = distributorContract.try_NAME(); + if (!nameResult.reverted) { + distributor.name = nameResult.value; + } else distributor.name = "Unknown"; + + const versionResult = distributorContract.try_VERSION(); + if (!versionResult.reverted) distributor.version = versionResult.value; + else distributor.version = BigInt.fromI32(0); + + const tokenResult = distributorContract.try_token(); + if (!tokenResult.reverted) { + distributor.token = tokenResult.value; + const tokenContract = IERC20Metadata.bind(tokenResult.value); + + const symbolResult = tokenContract.try_symbol(); + if (!symbolResult.reverted) distributor.tokenSymbol = symbolResult.value; + else distributor.tokenSymbol = "Unknown"; + + const decimalsResult = tokenContract.try_decimals(); + if (!decimalsResult.reverted) + distributor.tokenDecimals = BigInt.fromI32(decimalsResult.value); + else distributor.tokenDecimals = BigInt.fromI32(18); + } else { + distributor.token = Bytes.fromI32(0); + distributor.tokenSymbol = "Unknown"; + distributor.tokenDecimals = BigInt.fromI32(18); + } + + const totalResult = distributorContract.try_total(); + if (!totalResult.reverted) { + distributor.total = totalResult.value; + distributor.remaining = totalResult.value; + } else { + distributor.total = BigInt.fromI32(0); + distributor.remaining = BigInt.fromI32(0); + } + + const uriResult = distributorContract.try_uri(); + if (!uriResult.reverted) distributor.uris = [uriResult.value]; + else distributor.uris = ["unknown"]; + + const fractionDenominatorResult = distributorContract.try_getFractionDenominator(); + if (fractionDenominatorResult.reverted) { + // default to a fraction denominator of 10000 (fractions are specified in basis points) + distributor.fractionDenominator = BigInt.fromI32(10000); + } else { + distributor.fractionDenominator = fractionDenominatorResult.value; + } + + distributor.claimed = BigInt.fromI32(0); + distributor.createdAt = block.timestamp; + distributor.save(); + } + + return distributor; } export function getAdvancedDistributor(address: Address): AdvancedDistributor { - const advancedDistributor = AdvancedDistributor.load(`${address.toHexString()}-advanced`); - if (!advancedDistributor) throw new Error('advancedDistributor not found with id: ' + address.toHexString()); - return advancedDistributor; + const advancedDistributor = AdvancedDistributor.load( + `${address.toHexString()}-advanced` + ); + if (!advancedDistributor) + throw new Error( + "advancedDistributor not found with id: " + address.toHexString() + ); + return advancedDistributor; } -export function getOrCreateAdvancedDistributor(distributorId: string, block: ethereum.Block): AdvancedDistributor { - const distributorAddress: Address = Address.fromString(distributorId) - const advancedDistributorId = `${distributorId}-advanced`; - - let advancedDistributor = AdvancedDistributor.load(advancedDistributorId); - - if (advancedDistributor) { - // nothing needs to be set up - return advancedDistributor - } - - // get the base distributor - let distributor = Distributor.load(distributorId); - if (!distributor) { - distributor = getOrCreateDistributor(distributorAddress, block) - } - - // update the total - const distributorContract = IDistributor.bind(distributorAddress); - const totalResult = distributorContract.try_total(); - if (!totalResult.reverted) { - distributor.total = totalResult.value; - } - - // update the uri - const uriResult = distributorContract.try_uri(); - if (!uriResult.reverted) { - let uris = distributor.uris - uris.unshift(uriResult.value) - distributor.uris = uris - } - - distributor.save() - - // start listening to the distributor address for advanced distributor events - AdvancedDistributorTemplate.create(distributorAddress) - - // bind to the address for contract calls - const advancedDistributorContract = AdvancedDistributorContract.bind(distributorAddress); - - // create a new entity in the graphql database - advancedDistributor = new AdvancedDistributor(distributorId + '-advanced'); - advancedDistributor.distributor = distributorId - - const ownerResult = advancedDistributorContract.try_owner(); - advancedDistributor.owner = ownerResult.reverted ? null : getOrCreateAccount(ownerResult.value, block).id; - - // get sweep recipient - const sweepRecipient = advancedDistributorContract.try_getSweepRecipient(); - advancedDistributor.sweepRecipient = sweepRecipient.reverted ? null : getOrCreateAccount(sweepRecipient.value, block).id; - - // the address parameter here can be any value (not used at present) - const voteFactorResult = advancedDistributorContract.try_getVoteFactor(distributorAddress); - if (!voteFactorResult.reverted) advancedDistributor.voteFactor = voteFactorResult.value; - - advancedDistributor.createdAt = block.timestamp; - advancedDistributor.save(); - - return advancedDistributor; +export function getOrCreateAdvancedDistributor( + distributorId: string, + block: ethereum.Block +): AdvancedDistributor { + const distributorAddress: Address = Address.fromString(distributorId); + const advancedDistributorId = `${distributorId}-advanced`; + + let advancedDistributor = AdvancedDistributor.load(advancedDistributorId); + + if (advancedDistributor) { + // nothing needs to be set up + return advancedDistributor; + } + + // get the base distributor + let distributor = Distributor.load(distributorId); + if (!distributor) { + distributor = getOrCreateDistributor(distributorAddress, block); + } + + // update the total + const distributorContract = IDistributor.bind(distributorAddress); + const totalResult = distributorContract.try_total(); + if (!totalResult.reverted) { + distributor.total = totalResult.value; + } + + // update the uri + const uriResult = distributorContract.try_uri(); + if (!uriResult.reverted) { + let uris = distributor.uris; + uris.unshift(uriResult.value); + distributor.uris = uris; + } + + distributor.save(); + + // start listening to the distributor address for advanced distributor events + AdvancedDistributorTemplate.create(distributorAddress); + + // bind to the address for contract calls + const advancedDistributorContract = AdvancedDistributorContract.bind( + distributorAddress + ); + + // create a new entity in the graphql database + advancedDistributor = new AdvancedDistributor(distributorId + "-advanced"); + advancedDistributor.distributor = distributorId; + + const ownerResult = advancedDistributorContract.try_owner(); + advancedDistributor.owner = ownerResult.reverted + ? null + : getOrCreateAccount(ownerResult.value, block).id; + + // get sweep recipient + const sweepRecipient = advancedDistributorContract.try_getSweepRecipient(); + advancedDistributor.sweepRecipient = sweepRecipient.reverted + ? null + : getOrCreateAccount(sweepRecipient.value, block).id; + + // the address parameter here can be any value (not used at present) + const voteFactorResult = advancedDistributorContract.try_getVoteFactor( + distributorAddress + ); + if (!voteFactorResult.reverted) + advancedDistributor.voteFactor = voteFactorResult.value; + + advancedDistributor.createdAt = block.timestamp; + advancedDistributor.save(); + + return advancedDistributor; } - -export function getOrCreateTrancheVesting(distributorId: string, block: ethereum.Block): TrancheVesting | null { - log.info('Trying to add tranche vesting info for distributor {}', [distributorId]) - const distributorAddress: Address = Address.fromString(distributorId) - const distributorContract = ITrancheVesting.bind(distributorAddress) - - const tranchesResult = distributorContract.try_getTranches() - - if (tranchesResult.reverted) { - log.error('Could not call distributor.getTranches() on distributor {}, is it mis-registered as tranche vesting?', [distributorId]) - return null - } - - const trancheVestingId = `${distributorId}-trancheVesting` - let trancheVesting: TrancheVesting | null = TrancheVesting.load(trancheVestingId) - if (!trancheVesting) { - TrancheVestingTemplate.create(distributorAddress) - trancheVesting = new TrancheVesting(trancheVestingId) - trancheVesting.distributor = distributorId - trancheVesting.createdAt = block.timestamp - trancheVesting.save() - } - - - // add new tranches - for (let i = 0; i < tranchesResult.value.length; i++) { - const tranche = tranchesResult.value[i] - const trancheId = `${distributorId}-trancheVesting-${i.toString()}` - let t = Tranche.load(trancheId) - - if (!t) { - t = new Tranche(trancheId) - t.createdAt = block.timestamp - } - - t.trancheVesting = trancheVestingId - t.index = BigInt.fromI32(i) - t.time = tranche.time - t.vestedFraction = tranche.vestedFraction - t.save() - } - - // remove old tranches if they exist - let checkForMoreTranches = true - let i = tranchesResult.value.length - while (checkForMoreTranches) { - const trancheId = trancheVestingId + `-${i}` - let foundTranche = Tranche.load(trancheId); - - if (foundTranche) { - store.remove('Tranche', foundTranche.id); - i++; - } else { - checkForMoreTranches = false - } - } - - return trancheVesting +export function getOrCreateTrancheVesting( + distributorId: string, + block: ethereum.Block +): TrancheVesting | null { + log.info("Trying to add tranche vesting info for distributor {}", [ + distributorId + ]); + const distributorAddress: Address = Address.fromString(distributorId); + const distributorContract = ITrancheVesting.bind(distributorAddress); + + const tranchesResult = distributorContract.try_getTranches(); + + if (tranchesResult.reverted) { + log.error( + "Could not call distributor.getTranches() on distributor {}, is it mis-registered as tranche vesting?", + [distributorId] + ); + return null; + } + + const trancheVestingId = `${distributorId}-trancheVesting`; + let trancheVesting: TrancheVesting | null = TrancheVesting.load( + trancheVestingId + ); + if (!trancheVesting) { + TrancheVestingTemplate.create(distributorAddress); + trancheVesting = new TrancheVesting(trancheVestingId); + trancheVesting.distributor = distributorId; + trancheVesting.createdAt = block.timestamp; + trancheVesting.save(); + } + + // add new tranches + for (let i = 0; i < tranchesResult.value.length; i++) { + const tranche = tranchesResult.value[i]; + const trancheId = `${distributorId}-trancheVesting-${i.toString()}`; + let t = Tranche.load(trancheId); + + if (!t) { + t = new Tranche(trancheId); + t.createdAt = block.timestamp; + } + + t.trancheVesting = trancheVestingId; + t.index = BigInt.fromI32(i); + t.time = tranche.time; + t.vestedFraction = tranche.vestedFraction; + t.save(); + } + + // remove old tranches if they exist + let checkForMoreTranches = true; + let i = tranchesResult.value.length; + while (checkForMoreTranches) { + const trancheId = trancheVestingId + `-${i}`; + let foundTranche = Tranche.load(trancheId); + + if (foundTranche) { + store.remove("Tranche", foundTranche.id); + i++; + } else { + checkForMoreTranches = false; + } + } + + return trancheVesting; } -export function getOrCreatePriceTiers(distributorId: string, block: ethereum.Block): PriceTier[] | null { - log.info('Trying to add price tier vesting info to distributor {}', [distributorId]) - const distributorAddress: Address = Address.fromString(distributorId) - const distributorContract = IPriceTierVesting.bind(distributorAddress) - - const priceTiersResult = distributorContract.try_getPriceTiers() - - if (priceTiersResult.reverted) { - log.error('Could not call distributor.getPriceTiers() on distributor {}, is it mis-registered as tranche vesting?', [distributorId]) - return null - } - - const oracleResult = distributorContract.try_getOracle() - - if (oracleResult.reverted) { - log.error('Could not call distributor.getPriceTiers() on distributor {}, is it mis-registered as tranche vesting?', [distributorId]) - return null - } - - const priceTiers: PriceTier[] = [] - - for (let i = 0; i < priceTiersResult.value.length; i++) { - const priceTier = priceTiersResult.value[i] - const tierId = `${distributorId}-priceTierVesting-${priceTier.price}` - let t = PriceTier.load(tierId) - - if (!t) { - t = new PriceTier(tierId) - t.distributor = distributorId - t.price = priceTier.price - t.oracle = oracleResult.value.toHexString() - t.vestedFraction = priceTier.vestedFraction - t.createdAt = block.timestamp - t.save() - } - - priceTiers.push(t) - } - - // TODO: handle vesting updates? - return priceTiers +export function getOrCreatePriceTiers( + distributorId: string, + block: ethereum.Block +): PriceTier[] | null { + log.info("Trying to add price tier vesting info to distributor {}", [ + distributorId + ]); + const distributorAddress: Address = Address.fromString(distributorId); + const distributorContract = IPriceTierVesting.bind(distributorAddress); + + const priceTiersResult = distributorContract.try_getPriceTiers(); + + if (priceTiersResult.reverted) { + log.error( + "Could not call distributor.getPriceTiers() on distributor {}, is it mis-registered as tranche vesting?", + [distributorId] + ); + return null; + } + + const oracleResult = distributorContract.try_getOracle(); + + if (oracleResult.reverted) { + log.error( + "Could not call distributor.getPriceTiers() on distributor {}, is it mis-registered as tranche vesting?", + [distributorId] + ); + return null; + } + + const priceTiers: PriceTier[] = []; + + for (let i = 0; i < priceTiersResult.value.length; i++) { + const priceTier = priceTiersResult.value[i]; + const tierId = `${distributorId}-priceTierVesting-${priceTier.price}`; + let t = PriceTier.load(tierId); + + if (!t) { + t = new PriceTier(tierId); + t.distributor = distributorId; + t.price = priceTier.price; + t.oracle = oracleResult.value.toHexString(); + t.vestedFraction = priceTier.vestedFraction; + t.createdAt = block.timestamp; + t.save(); + } + + priceTiers.push(t); + } + + // TODO: handle vesting updates? + return priceTiers; } -export function getOrCreateContinuousVesting(distributorId: string, block: ethereum.Block): ContinuousVesting | null { - log.info('Trying to add continuous vesting info to distributor {}', [distributorId]) - const id = `${distributorId}-continuousVesting` - - let continuousVesting = ContinuousVesting.load(id); - - if (!continuousVesting) { - continuousVesting = new ContinuousVesting(id); - continuousVesting.createdAt = block.timestamp; - - const distributorAddress: Address = Address.fromString(distributorId) - const distributorContract = IContinuousVesting.bind(distributorAddress) - const result = distributorContract.try_getVestingConfig() - - if (result.reverted) { - log.error('Could not call distributor.getVestingConfig() on distributor {}, is it mis-registered as continuous vesting?', [distributorId]) - return null - } - - continuousVesting.start = result.value.value0 - continuousVesting.cliff = result.value.value1 - continuousVesting.end = result.value.value2 - continuousVesting.distributor = distributorId - continuousVesting.save() - } else { - log.info('**** CONTINUOUSVESTING {} {}', [continuousVesting.id, continuousVesting.createdAt.toString()]) - } - - return continuousVesting +export function getOrCreateContinuousVesting( + distributorId: string, + block: ethereum.Block +): ContinuousVesting | null { + log.info("Trying to add continuous vesting info to distributor {}", [ + distributorId + ]); + const id = `${distributorId}-continuousVesting`; + + let continuousVesting = ContinuousVesting.load(id); + + if (!continuousVesting) { + continuousVesting = new ContinuousVesting(id); + continuousVesting.createdAt = block.timestamp; + + const distributorAddress: Address = Address.fromString(distributorId); + const distributorContract = IContinuousVesting.bind(distributorAddress); + const result = distributorContract.try_getVestingConfig(); + + if (result.reverted) { + log.error( + "Could not call distributor.getVestingConfig() on distributor {}, is it mis-registered as continuous vesting?", + [distributorId] + ); + return null; + } + + continuousVesting.start = result.value.value0; + continuousVesting.cliff = result.value.value1; + continuousVesting.end = result.value.value2; + continuousVesting.distributor = distributorId; + continuousVesting.save(); + } else { + log.info("**** CONTINUOUSVESTING {} {}", [ + continuousVesting.id, + continuousVesting.createdAt.toString() + ]); + } + + return continuousVesting; } -export function getOrCreateMerkleSet(distributorId: string, block: ethereum.Block): MerkleSet | null { - log.info('Trying to add merkle set info to distributor {}', [distributorId]) - const id = `${distributorId}-merkleSet` - - let merkleSet = MerkleSet.load(id); - - if (!merkleSet) { - log.info('new Merkle Set {}', [id]) - MerkleSetTemplate.create(Address.fromString(distributorId)) - const distributorAddress: Address = Address.fromString(distributorId) - const merkleSetContract = IMerkleSet.bind(distributorAddress) - - merkleSet = new MerkleSet(id); - const getMerkleRootResult = merkleSetContract.try_getMerkleRoot() - - if (getMerkleRootResult.reverted) { - log.error('Could not call getMerkleRoot() on {}, is it an IMerkleSet contract?', [distributorId]) - return null - } - - merkleSet.distributor = distributorId - merkleSet.root = getMerkleRootResult.value.toHexString() - merkleSet.createdAt = block.timestamp; - merkleSet.save() - } - - return merkleSet +export function getOrCreateMerkleSet( + distributorId: string, + block: ethereum.Block +): MerkleSet | null { + log.info("Trying to add merkle set info to distributor {}", [distributorId]); + const id = `${distributorId}-merkleSet`; + + let merkleSet = MerkleSet.load(id); + + if (!merkleSet) { + log.info("new Merkle Set {}", [id]); + MerkleSetTemplate.create(Address.fromString(distributorId)); + const distributorAddress: Address = Address.fromString(distributorId); + const merkleSetContract = IMerkleSet.bind(distributorAddress); + + merkleSet = new MerkleSet(id); + const getMerkleRootResult = merkleSetContract.try_getMerkleRoot(); + + if (getMerkleRootResult.reverted) { + log.error( + "Could not call getMerkleRoot() on {}, is it an IMerkleSet contract?", + [distributorId] + ); + return null; + } + + merkleSet.distributor = distributorId; + merkleSet.root = getMerkleRootResult.value.toHexString(); + merkleSet.createdAt = block.timestamp; + merkleSet.save(); + } + + return merkleSet; } /** * Get an existing sale or throw */ export function getSale(saleId: string): Sale { - const sale = Sale.load(saleId); - if (!sale) throw new Error('sale not found with id: ' + saleId); - return sale; + const sale = Sale.load(saleId); + if (!sale) throw new Error("sale not found with id: " + saleId); + return sale; } /** * Get an existing sale implementation (sale manager for v1.x contracts) or throw */ export function getSaleImplementation(id: Address): SaleImplementation { - const saleImplementation = SaleImplementation.load(id.toHexString()); - if (!saleImplementation) throw new Error('saleImplementation not found with id: ' + id.toHexString()); - return saleImplementation; + const saleImplementation = SaleImplementation.load(id.toHexString()); + if (!saleImplementation) + throw new Error( + "saleImplementation not found with id: " + id.toHexString() + ); + return saleImplementation; } export function getDistributor(id: Address): Distributor { - const distributor = Distributor.load(id.toHexString()); - if (!distributor) throw new Error('distributor not found with id: ' + id.toHexString()); - return distributor; + const distributor = Distributor.load(id.toHexString()); + if (!distributor) + throw new Error("distributor not found with id: " + id.toHexString()); + return distributor; } /** * Create a distribution record or throw */ -export function createDistributionRecord(distributorAddress: Address, beneficiaryAddress: Address, block: ethereum.Block): DistributionRecord { - const distributionRecordId = distributorAddress.toHexString() + '-' + beneficiaryAddress.toHexString(); - let distributionRecord = DistributionRecord.load(distributionRecordId); - - if (distributionRecord) throw new Error('distribution record already exists with id: ' + distributionRecordId); - - distributionRecord = new DistributionRecord(distributionRecordId); - distributionRecord.distributor = distributorAddress.toHexString(); +export function createDistributionRecord( + distributorAddress: Address, + beneficiaryAddress: Address, + block: ethereum.Block +): DistributionRecord { + const distributionRecordId = + distributorAddress.toHexString() + "-" + beneficiaryAddress.toHexString(); + let distributionRecord = DistributionRecord.load(distributionRecordId); + + if (distributionRecord) + throw new Error( + "distribution record already exists with id: " + distributionRecordId + ); + + distributionRecord = new DistributionRecord(distributionRecordId); + distributionRecord.distributor = distributorAddress.toHexString(); const beneficiaryAccount = getOrCreateAccount(beneficiaryAddress, block); - distributionRecord.beneficiary = beneficiaryAccount.id; - distributionRecord.claimed = BigInt.fromI32(0); - distributionRecord.createdAt = block.timestamp; - distributionRecord.save(); + distributionRecord.beneficiary = beneficiaryAccount.id; + distributionRecord.claimed = BigInt.fromI32(0); + distributionRecord.createdAt = block.timestamp; + distributionRecord.save(); - return distributionRecord; + return distributionRecord; } -export function getDistributionRecord(distributorAddress: Address, beneficiaryAddress: Address): DistributionRecord { - const distributionRecordId = distributorAddress.toHexString() + '-' + beneficiaryAddress.toHexString(); - let distributionRecord = DistributionRecord.load(distributionRecordId); - if (!distributionRecord) throw new Error('distributionRecord not found with id: ' + distributionRecordId); - return distributionRecord +export function getDistributionRecord( + distributorAddress: Address, + beneficiaryAddress: Address +): DistributionRecord { + const distributionRecordId = + distributorAddress.toHexString() + "-" + beneficiaryAddress.toHexString(); + let distributionRecord = DistributionRecord.load(distributionRecordId); + if (!distributionRecord) + throw new Error( + "distributionRecord not found with id: " + distributionRecordId + ); + return distributionRecord; } -export function getOrCreateDistributionRecord(distributorAddress: Address, beneficiaryAddress: Address, block: ethereum.Block): DistributionRecord { - const distributionRecordId = distributorAddress.toHexString() + '-' + beneficiaryAddress.toHexString(); - let distributionRecord = DistributionRecord.load(distributionRecordId); - if (!distributionRecord) { - return createDistributionRecord(distributorAddress, beneficiaryAddress, block) - } - - return distributionRecord +export function getOrCreateDistributionRecord( + distributorAddress: Address, + beneficiaryAddress: Address, + block: ethereum.Block +): DistributionRecord { + const distributionRecordId = + distributorAddress.toHexString() + "-" + beneficiaryAddress.toHexString(); + let distributionRecord = DistributionRecord.load(distributionRecordId); + if (!distributionRecord) { + return createDistributionRecord( + distributorAddress, + beneficiaryAddress, + block + ); + } + + return distributionRecord; } /** * Get or create a registry */ -export function getOrCreateRegistry(registryId: string, block: ethereum.Block): Registry { - let registry = Registry.load(registryId); - - if (!registry) { - registry = new Registry(registryId); - registry.owner = Address.fromI32(0).toHexString(); - registry.admins = []; - registry.createdAt = block.timestamp; - registry.save() - } - - return registry +export function getOrCreateRegistry( + registryId: string, + block: ethereum.Block +): Registry { + let registry = Registry.load(registryId); + + if (!registry) { + registry = new Registry(registryId); + registry.owner = Address.fromI32(0).toHexString(); + registry.admins = []; + registry.createdAt = block.timestamp; + registry.save(); + } + + return registry; } /** * Get or create a registered contract */ export function getOrCreateRegisteredAddress( - registeredAddressId: string, - block: ethereum.Block, - registryId: string + registeredAddressId: string, + block: ethereum.Block, + registryId: string ): RegisteredAddress { - let registeredAddress = RegisteredAddress.load(registeredAddressId); + let registeredAddress = RegisteredAddress.load(registeredAddressId); - if (registeredAddress) return registeredAddress + if (registeredAddress) return registeredAddress; - registeredAddress = new RegisteredAddress(registeredAddressId); - registeredAddress.registry = registryId; - registeredAddress.interfaceIds = []; - registeredAddress.interfaceNames = []; - registeredAddress.createdAt = block.timestamp; - registeredAddress.save() + registeredAddress = new RegisteredAddress(registeredAddressId); + registeredAddress.registry = registryId; + registeredAddress.interfaceIds = []; + registeredAddress.interfaceNames = []; + registeredAddress.createdAt = block.timestamp; + registeredAddress.save(); - return registeredAddress + return registeredAddress; } -export function getRegisteredAddress(registeredAddressId: string): RegisteredAddress { - const registeredAddress = RegisteredAddress.load(registeredAddressId); - if (!registeredAddress) throw new Error('registeredAddress not found with id: ' + registeredAddressId); - return registeredAddress; +export function getRegisteredAddress( + registeredAddressId: string +): RegisteredAddress { + const registeredAddress = RegisteredAddress.load(registeredAddressId); + if (!registeredAddress) + throw new Error( + "registeredAddress not found with id: " + registeredAddressId + ); + return registeredAddress; } /** * Create a claim or throw */ -export function createClaim(transaction: ethereum.Transaction, distributionRecord: DistributionRecord, beneficiaryAddress: Address, amount: BigInt, uri: string, block: ethereum.Block): Claim { - const claimId = transaction.hash.toHexString() + '-' + transaction.index.toHexString(); - let claim = Claim.load(claimId); - - if (claim) throw new Error('claim already exists with id: ' + claimId); - - claim = new Claim(claimId); - claim.distributionRecord = distributionRecord.id; - claim.amount = amount; - claim.transactionHash = transaction.hash.toHexString(); - claim.createdAt = block.timestamp; - claim.uri = uri; - claim.save() - - return claim +export function createClaim( + transaction: ethereum.Transaction, + distributionRecord: DistributionRecord, + beneficiaryAddress: Address, + amount: BigInt, + uri: string, + block: ethereum.Block +): Claim { + const claimId = + transaction.hash.toHexString() + "-" + transaction.index.toHexString(); + let claim = Claim.load(claimId); + + if (claim) throw new Error("claim already exists with id: " + claimId); + + claim = new Claim(claimId); + claim.distributionRecord = distributionRecord.id; + claim.amount = amount; + claim.transactionHash = transaction.hash.toHexString(); + claim.createdAt = block.timestamp; + claim.uri = uri; + claim.save(); + + return claim; } /** * Create an adjustment or throw */ -export function createAdjustment(transaction: ethereum.Transaction, distributionRecord: DistributionRecord, amount: BigInt, uri: string, block: ethereum.Block): Adjustment { - const adjustmentId = transaction.hash.toHexString() + '-' + transaction.index.toHexString(); - let adjustment = Adjustment.load(adjustmentId); - - if (adjustment) throw new Error('adjustment already exists with id: ' + adjustmentId); - - adjustment = new Adjustment(adjustmentId); - adjustment.distributionRecord = distributionRecord.id; - adjustment.amount = amount; - adjustment.transactionHash = transaction.hash.toHexString(); - adjustment.createdAt = block.timestamp; - adjustment.uri = uri; - adjustment.save(); - - return adjustment; +export function createAdjustment( + transaction: ethereum.Transaction, + distributionRecord: DistributionRecord, + amount: BigInt, + uri: string, + block: ethereum.Block +): Adjustment { + const adjustmentId = + transaction.hash.toHexString() + "-" + transaction.index.toHexString(); + let adjustment = Adjustment.load(adjustmentId); + + if (adjustment) + throw new Error("adjustment already exists with id: " + adjustmentId); + + adjustment = new Adjustment(adjustmentId); + adjustment.distributionRecord = distributionRecord.id; + adjustment.amount = amount; + adjustment.transactionHash = transaction.hash.toHexString(); + adjustment.createdAt = block.timestamp; + adjustment.uri = uri; + adjustment.save(); + + return adjustment; } export const hardCodedSaleIds = [ - '0x79c57db44f21229c5f149436dd2fa5f6827ff62441f585b8d792304873ce2d70', - '0x9ae7fa4e0049fa03d2f30409f22b3f88a7bc2497360005e1018855976463f474', - '0x537d3a85d36c4780751a7ad53edc7018dfeb9a1a5624ff7c1dc6d4e04952a8b4', - '0xf2d9d6fbe8e7b425e8f8d6d22184b50c110cdcc9a2ec421a331b072193844db8', - '0xa0ba0b802d948555579978276aab3baf0f52818635921301f9ecc91c60ade191', - '0xe5a50f47a6a861c0a7c09dbefde327b784b5a1183df6264b38aeebdc267e5b91', - '0x8cbd028ed628967c27c8399f02265c2807db4c0fa7604ea0b355424a8dd3273b' -] + "0x79c57db44f21229c5f149436dd2fa5f6827ff62441f585b8d792304873ce2d70", + "0x9ae7fa4e0049fa03d2f30409f22b3f88a7bc2497360005e1018855976463f474", + "0x537d3a85d36c4780751a7ad53edc7018dfeb9a1a5624ff7c1dc6d4e04952a8b4", + "0xf2d9d6fbe8e7b425e8f8d6d22184b50c110cdcc9a2ec421a331b072193844db8", + "0xa0ba0b802d948555579978276aab3baf0f52818635921301f9ecc91c60ade191", + "0xe5a50f47a6a861c0a7c09dbefde327b784b5a1183df6264b38aeebdc267e5b91", + "0x8cbd028ed628967c27c8399f02265c2807db4c0fa7604ea0b355424a8dd3273b" +]; export const hardCodedSaleUris = [ - 'ipfs://QmWf3q5g7z5BVBny1Kc1UEZ6uQHxmyNrMdtZCPM2BPCfvc', - 'ipfs://QmW24dQnucNUF5sQMURF4AjZvSp6FUkF1JvgPc69s9tiuP', - 'ipfs://QmWMkzNNsQwyur3VmQhWxMzyi9nRJZevqrTJp7AGe81P5F', - 'ipfs://QmWH4oidyX42tWDqMQymxJwxnHbAwvdfYaUHVNa4Bwg1iz', - 'ipfs://QmQtZn9sa6kZPHc5JBFMTUV6RojbLQ1NsREo5GJy3GFPKN', - 'ipfs://QmQFnVeiwDn2rsh4spzuZdfvSLCJ2KkMqRizucqx1tDtpd', - 'ipfs://QmXPgQzZWXLcZQH6vdUppghAWZe2n4fXitWpy3jKQBPniu' -] + "ipfs://QmWf3q5g7z5BVBny1Kc1UEZ6uQHxmyNrMdtZCPM2BPCfvc", + "ipfs://QmW24dQnucNUF5sQMURF4AjZvSp6FUkF1JvgPc69s9tiuP", + "ipfs://QmWMkzNNsQwyur3VmQhWxMzyi9nRJZevqrTJp7AGe81P5F", + "ipfs://QmWH4oidyX42tWDqMQymxJwxnHbAwvdfYaUHVNa4Bwg1iz", + "ipfs://QmQtZn9sa6kZPHc5JBFMTUV6RojbLQ1NsREo5GJy3GFPKN", + "ipfs://QmQFnVeiwDn2rsh4spzuZdfvSLCJ2KkMqRizucqx1tDtpd", + "ipfs://QmXPgQzZWXLcZQH6vdUppghAWZe2n4fXitWpy3jKQBPniu" +]; // check if any interfaces are contained in both lists -export const interfacesMatch = (interfaces1: string[], interfaces2: string[]): boolean => { - for (let i = 0; i < interfaces1.length; i++) { - if (interfaces2.indexOf(interfaces1[i]) !== -1) { - return true - } - } - return false -} - -export const createCrosschainDistributor = (distributorId: string):void => { - const distributorAddress: Address = Address.fromString(distributorId); - - CrosschainDistributor.create(distributorAddress); -} \ No newline at end of file +export const interfacesMatch = ( + interfaces1: string[], + interfaces2: string[] +): boolean => { + for (let i = 0; i < interfaces1.length; i++) { + if (interfaces2.indexOf(interfaces1[i]) !== -1) { + return true; + } + } + return false; +}; + +export const createCrosschainDistributor = (distributorId: string): void => { + const distributorAddress: Address = Address.fromString(distributorId); + + CrosschainDistributor.create(distributorAddress); +}; diff --git a/packages/subgraph/src/sale/v4.0/flatPriceSaleFactoryMapping.ts b/packages/subgraph/src/sale/v4.0/flatPriceSaleFactoryMapping.ts new file mode 100644 index 00000000..06167f04 --- /dev/null +++ b/packages/subgraph/src/sale/v4.0/flatPriceSaleFactoryMapping.ts @@ -0,0 +1,55 @@ +import { BigInt } from "@graphprotocol/graph-ts"; +import { NewSale } from "../../../generated/FlatPriceSaleFactory_v_4_0/FlatPriceSaleFactory_v_4_0"; +import { getOrCreateAccount, getOrCreateNativePaymentMethod } from "../../lib"; +import { SaleImplementation, Sale } from "../../../generated/schema"; +import { FlatPriceSale_v_4_0 } from "../../../generated/templates"; + +// When the clone factory clones an existing implementation contract, save the sale +export function handleNewSale(event: NewSale): void { + FlatPriceSale_v_4_0.create(event.params.clone); + + const implementationId = event.params.implementation.toHexString(); + const saleImplementation = SaleImplementation.load(implementationId); + + const saleId = event.params.clone.toHexString(); + + let sale = Sale.load(saleId); + if (sale) { + throw new Error("cloning to an existing sale at: " + saleId); + } + + sale = new Sale(saleId); + sale.idBytes = event.params.clone; + sale.implementation = implementationId; + sale.owner = getOrCreateAccount(event.transaction.from, event.block).id; + + // save config + sale.recipient = getOrCreateAccount( + event.params.config.recipient, + event.block + ).id; + sale.merkleRoot = event.params.config.merkleRoot; + sale.saleMaximum = event.params.config.saleMaximum; + sale.userMaximum = event.params.config.userMaximum; + sale.purchaseMinimum = event.params.config.purchaseMinimum; + sale.startTime = event.params.config.startTime; + sale.endTime = event.params.config.endTime; + sale.maxQueueTime = event.params.config.maxQueueTime; + sale.uris = [event.params.config.URI]; + + // save payment info + sale.baseCurrency = event.params.baseCurrency; + sale.paymentMethods = event.params.nativePaymentsEnabled + ? [ + getOrCreateNativePaymentMethod(event.params.nativeOracle, event.block) + .id + ] + : []; + + sale.purchaseCount = BigInt.fromI32(0); + sale.purchaseTotal = BigInt.fromI32(0); + + sale.createdAt = event.block.timestamp; + + sale.save(); +} diff --git a/packages/subgraph/src/sale/v4.0/saleImplementationMapping.ts b/packages/subgraph/src/sale/v4.0/saleImplementationMapping.ts new file mode 100644 index 00000000..316c3c36 --- /dev/null +++ b/packages/subgraph/src/sale/v4.0/saleImplementationMapping.ts @@ -0,0 +1,32 @@ +import { + ImplementationConstructor, + FlatPriceSale_v_4_0 as FlatPriceSaleContract +} from "../../../generated/FlatPriceSale_v_4_0/FlatPriceSale_v_4_0"; +import { SaleImplementation } from "../../../generated/schema"; +import { log, BigInt } from "@graphprotocol/graph-ts"; + +export function handleImplementationConstructor( + event: ImplementationConstructor +): void { + const saleImplementationId = event.address.toHexString(); + + const implementation = new SaleImplementation(saleImplementationId); + const implementationContract = FlatPriceSaleContract.bind(event.address); + + implementation.networkConfig = event.params.networkConfig.toHexString(); + implementation.saleCount = BigInt.fromI32(0); + implementation.purchaseCount = BigInt.fromI32(0); + const versionResult = implementationContract.try_VERSION(); + + if (versionResult.reverted) { + log.error("could not call VERSION() on implementation at {} in tx {}", [ + saleImplementationId, + event.transaction.hash.toHexString() + ]); + return; + } + + implementation.version = versionResult.value; + implementation.createdAt = event.block.timestamp; + implementation.save(); +} diff --git a/packages/subgraph/src/sale/v4.0/saleMapping.ts b/packages/subgraph/src/sale/v4.0/saleMapping.ts new file mode 100644 index 00000000..e21d7970 --- /dev/null +++ b/packages/subgraph/src/sale/v4.0/saleMapping.ts @@ -0,0 +1,234 @@ +import { + SweepNative, + SweepToken, + Initialize, + Update, + SetPaymentTokenInfo, + Buy, + OwnershipTransferred, + RegisterDistributor, + FlatPriceSale_v_4_0 as FlatPriceSaleContract +} from "../../../generated/FlatPriceSale_v_4_0/FlatPriceSale_v_4_0"; + +import { + SaleImplementation, + PaymentMethod, + Sale, + Purchase +} from "../../../generated/schema"; +import { log, BigInt, ByteArray } from "@graphprotocol/graph-ts"; +import { + getOrCreateAccount, + getOrCreateNativePaymentMethod, + getOrCreateTokenPaymentMethod, + getSale, + getOrCreateDistributor +} from "../../lib"; + +export function handleInitialize(event: Initialize): void { + // We are now handling the initialization in the sale factory event + const saleId = event.address.toHexString(); + + const sale = Sale.load(saleId); + if (!sale) { + log.error("no sale with id {} in tx {}", [ + saleId, + event.transaction.hash.toHexString() + ]); + } +} +export function handleSetPaymentTokenInfo(event: SetPaymentTokenInfo): void { + // only called during sale initialization + const saleId = event.address.toHexString(); + const sale = Sale.load(saleId); + if (!sale) { + log.error("missing sale {}", [saleId]); + return; + // throw new Error('missing sale: ' + saleId); + } + + const paymentMethod = getOrCreateTokenPaymentMethod( + event.params.token, + event.params.paymentTokenInfo.decimals, + event.params.paymentTokenInfo.oracle, + event.block + ); + + const paymentMethods = sale.paymentMethods; + paymentMethods.push(paymentMethod.id); + sale.paymentMethods = paymentMethods; + sale.save(); +} + +export function handleUpdate(event: Update): void { + const saleId = event.address.toHexString(); + const sale = Sale.load(saleId); + if (!sale) { + log.error("missing sale {}", [saleId]); + return; + // throw new Error('no sale to update: ' + saleId); + } + + // save config + sale.recipient = getOrCreateAccount( + event.params.config.recipient, + event.block + ).id; + sale.merkleRoot = event.params.config.merkleRoot; + sale.saleMaximum = event.params.config.saleMaximum; + sale.userMaximum = event.params.config.userMaximum; + sale.purchaseMinimum = event.params.config.purchaseMinimum; + sale.startTime = event.params.config.startTime; + sale.endTime = event.params.config.endTime; + sale.maxQueueTime = event.params.config.maxQueueTime; + + const uris = sale.uris; + // only save novel URIs + if (uris[0] != event.params.config.URI) { + uris.unshift(event.params.config.URI); + } + sale.uris = uris; + sale.save(); +} + +export function handleOwnershipTransferred(event: OwnershipTransferred): void { + // Note that this is called during initialization! + // TODO: fix this, the matching sale isn't being found in the subgraph + log.info("ownership transfered at address {} from {} to {}", [ + event.address.toHexString(), + event.params.previousOwner.toHexString(), + event.params.newOwner.toHexString() + ]); + // const saleId = event.address.toHexString(); + // const sale = Sale.load(saleId); + // if (!sale) { + // throw new Error('missing sale: ' + saleId); + // } + + // const ownerId = event.params.newOwner.toHexString(); + + // let owner = Account.load(ownerId); + + // if (owner === null) { + // owner = new Account(ownerId); + // owner.createdAt = event.block.timestamp; + // owner.save() + // } + + // sale.owner = owner.id; + // sale.save(); +} + +export function handleBuy(event: Buy): void { + // Query the contract to get the payment token info + const saleContract = FlatPriceSaleContract.bind(event.address); + + // The buy event uses token == address(0) to signify a native purchase + let paymentMethod: PaymentMethod; + const isNative = + event.params.token.toHexString() == + "0x0000000000000000000000000000000000000000"; + if (isNative) { + // native payment + const nativeOracle = saleContract.nativeTokenPriceOracle(); + const nativeOracleHeartbeat = saleContract.nativeTokenPriceOracleHeartbeat(); + paymentMethod = getOrCreateNativePaymentMethod(nativeOracle, event.block); + } else { + // ERC20 payment + const paymentTokenInfo = saleContract.getPaymentToken(event.params.token); + paymentMethod = getOrCreateTokenPaymentMethod( + event.params.token, + paymentTokenInfo.decimals, + paymentTokenInfo.oracle, + event.block + ); + } + + const saleId = event.address.toHexString(); + const sale = Sale.load(saleId); + if (!sale) { + log.error("missing sale {}", [saleId]); + return; + // throw new Error('no sale to update: ' + saleId); + } + + const buyer = getOrCreateAccount(event.params.buyer, event.block); + + let price: BigInt; + // calculate a price + if (event.params.tokenValue > BigInt.fromI32(0)) { + // calculate the price from the transaction + price = event.params.baseCurrencyValue.times( + BigInt.fromI32(10) + .pow(paymentMethod.decimals as u8) + .div(event.params.tokenValue) + ); + } else { + // fallback for 0 value transactions + price = BigInt.fromI32(0); + } + + // Create a new purchase + const id = + event.transaction.hash.toHexString() + "-" + event.logIndex.toString(); + let purchase = new Purchase(id); + purchase.sale = sale.id; + purchase.buyer = buyer.id; + purchase.baseCurrencyValue = event.params.baseCurrencyValue; + purchase.paymentMethod = paymentMethod.id; + purchase.price = price; + purchase.spent = event.params.tokenValue; + purchase.protocolTokenFee = event.params.protocolTokenFee; + purchase.platformTokenFee = event.params.platformTokenFee; + purchase.transactionHash = event.transaction.hash.toHexString(); + purchase.createdAt = event.block.timestamp; + // copy the uri at the time of purchase to this record (reference for purchased token price) + purchase.uri = sale.uris[0]; + purchase.save(); + + // update the sale metrics + sale.purchaseTotal = sale.purchaseTotal.plus(purchase.baseCurrencyValue); + sale.purchaseCount = sale.purchaseCount.plus(BigInt.fromI32(1)); + sale.save(); + + // update the payment method metrics + paymentMethod.purchaseTotal = paymentMethod.purchaseTotal.plus( + purchase.spent + ); + paymentMethod.purchaseCount = paymentMethod.purchaseCount.plus( + BigInt.fromI32(1) + ); + paymentMethod.save(); + + // update the sale implementation metrics + const implementation = SaleImplementation.load(sale.implementation); + if (!implementation) { + log.error("missing implementation {}", [sale.implementation]); + return; + // throw new Error('no sale to update: ' + sale.implementation); + } + + implementation.purchaseCount = implementation.purchaseCount.plus( + BigInt.fromI32(1) + ); + implementation.save(); +} + +export function handleSweepToken(event: SweepToken): void { + // TODO: handle sweeps +} + +export function handleSweepNative(event: SweepNative): void { + // TODO: handle sweeps +} + +export function handleRegisterDistributor(event: RegisterDistributor): void { + const sale = getSale(event.address.toHexString()); + const distributor = getOrCreateDistributor( + event.params.distributor, + event.block + ); + + sale.distributor = distributor.id; + sale.save(); +} diff --git a/packages/subgraph/src/subgraph.template.yaml b/packages/subgraph/src/subgraph.template.yaml index 7a7791ba..d300d55b 100644 --- a/packages/subgraph/src/subgraph.template.yaml +++ b/packages/subgraph/src/subgraph.template.yaml @@ -52,6 +52,32 @@ dataSources: - event: NewSale(indexed address,indexed address,(address,bytes32,uint256,uint256,uint256,uint256,uint256,uint256,string),string,address,uint256,bool) handler: handleNewSale {{/each}} + {{#each FlatPriceSaleFactory_v_4_0}} + - kind: ethereum/contract + {{#if @index}} + name: FlatPriceSaleFactory_v_4_0_{{@index}} + {{else}} + name: FlatPriceSaleFactory_v_4_0 + {{/if}} + network: {{../SubgraphNetwork}} + source: + address: '{{Address}}' + abi: FlatPriceSaleFactory_v_4_0 + startBlock: {{BlockNumber}} + mapping: + kind: ethereum/events + apiVersion: 0.0.6 + language: wasm/assemblyscript + file: ./src/sale/v4.0/flatPriceSaleFactoryMapping.ts + entities: + - Sale + abis: + - name: FlatPriceSaleFactory_v_4_0 + file: ./abis/contracts/sale/v4/FlatPriceSaleFactory.sol/FlatPriceSaleFactory_v_4_0.json + eventHandlers: + - event: NewSale(indexed address,indexed address,(address,bytes32,uint256,uint256,uint256,uint256,uint256,uint256,string),string,address,uint256,bool) + handler: handleNewSale + {{/each}} {{#each FlatPriceSale_v_3}} - kind: ethereum/contract {{#if @index}} @@ -78,6 +104,32 @@ dataSources: handler: handleImplementationConstructor file: ./src/sale/v3.0/saleImplementationMapping.ts {{/each}} + {{#each FlatPriceSale_v_4_0}} + - kind: ethereum/contract + {{#if @index}} + name: FlatPriceSale_v_4_0_{{@index}} + {{else}} + name: FlatPriceSale_v_4_0 + {{/if}} + network: {{../SubgraphNetwork}} + source: + address: '{{Address}}' + abi: FlatPriceSale_v_4_0 + startBlock: {{BlockNumber}} + mapping: + kind: ethereum/events + apiVersion: 0.0.6 + language: wasm/assemblyscript + entities: + - SaleImplementation + abis: + - name: FlatPriceSale_v_4_0 + file: ./abis/contracts/sale/v4/FlatPriceSale.sol/FlatPriceSale_v_4_0.json + eventHandlers: + - event: ImplementationConstructor(address) + handler: handleImplementationConstructor + file: ./src/sale/v4.0/saleImplementationMapping.ts + {{/each}} {{#each FlatPriceSaleFactory_v_2_1}} - kind: ethereum/contract {{#if @index}} @@ -800,3 +852,44 @@ templates: eventHandlers: - event: CrosschainClaim(indexed bytes32,indexed address,indexed address,uint32,uint256) handler: handleCrosschainClaim + + - kind: ethereum/contract + name: FlatPriceSale_v_4_0 + network: {{SubgraphNetwork}} + source: + abi: FlatPriceSale_v_4_0 + mapping: + kind: ethereum/events + apiVersion: 0.0.6 + language: wasm/assemblyscript + file: ./src/sale/v4.0/saleMapping.ts + entities: + - Purchase + - Sale + - Distributor + abis: + - name: FlatPriceSale_v_4_0 + file: ./abis/contracts/sale/v4/FlatPriceSale.sol/FlatPriceSale_v_4_0.json + - name: FlatPriceSaleFactory_v_4_0 + file: ./abis/contracts/sale/v4/FlatPriceSaleFactory.sol/FlatPriceSaleFactory_v_4_0.json + - name: IDistributor + file: ./abis/contracts/interfaces/IDistributor.sol/IDistributor.json + - name: IERC20Metadata + file: ./abis/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol/IERC20Metadata.json + eventHandlers: + - event: Buy(indexed address,indexed address,uint256,uint256,uint256,uint256) + handler: handleBuy + - event: Update((address,bytes32,uint256,uint256,uint256,uint256,uint256,uint256,string)) + handler: handleUpdate + - event: Initialize((address,bytes32,uint256,uint256,uint256,uint256,uint256,uint256,string),string,address,bool) + handler: handleInitialize + - event: SetPaymentTokenInfo(address,(address,uint256,uint8)) + handler: handleSetPaymentTokenInfo + - event: OwnershipTransferred(indexed address,indexed address) + handler: handleOwnershipTransferred + - event: SweepToken(indexed address,uint256) + handler: handleSweepToken + - event: SweepNative(uint256) + handler: handleSweepNative + - event: RegisterDistributor(address) + handler: handleRegisterDistributor \ No newline at end of file diff --git a/scripts/produceSignature.ts b/scripts/produceSignature.ts new file mode 100644 index 00000000..997be02c --- /dev/null +++ b/scripts/produceSignature.ts @@ -0,0 +1,44 @@ +import { encodePacked, keccak256 } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import dotenv from "dotenv"; + +// Load environment variables +dotenv.config(); + +const contractAddress = "0xB278E83ED314c6E42f80D33Eb55Df4c21Cf90497"; // Address of the contract containing verifyAccessSignature +const memberAddress = "0xb4B95fC47Bb797AcC777e5A2AA27c23C294637eE"; // Address of the member +const userLimit = 2000000000n; // Example user limit +// Expires in 7 days +// const expiresAt = BigInt(Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60); +// Expires at October 13th, 2024 at 11:59pm. +const expiresAt = BigInt(1728950399); + +console.log( + `values used: contractAddress: ${contractAddress}, memberAddress: ${memberAddress}, userLimit: ${userLimit}, expiresAt: ${expiresAt}` +); + +const message = encodePacked( + ["address", "address", "uint256", "uint64"], + [contractAddress, memberAddress, userLimit, expiresAt] +); +console.log(`message is: ${message}`); +const messageHash = keccak256(message); +console.log(`messageHash is: ${messageHash}`); + +// Add error checking for the private key +const privateKey = process.env.EVM_PRIVATE_KEY_1; // Replace with your actual private key +if (!privateKey || !privateKey.startsWith("0x")) { + throw new Error( + 'Invalid or missing private key. Make sure EVM_PRIVATE_KEY_1 is set in your .env file and starts with "0x".' + ); +} +const account = privateKeyToAccount(privateKey as `0x${string}`); + +async function signMessage() { + const signature = await account.signMessage({ + message: { raw: messageHash } + }); + console.log("Signature:", signature); +} + +signMessage().catch(console.error); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..2f22cc38 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "node", + "esModuleInterop": true, + "target": "ESNext", + "allowJs": true, + "strict": true, + "outDir": "./dist", + "rootDir": "." + }, + "ts-node": { + "esm": true, + "experimentalSpecifierResolution": "node" + }, + "include": ["scripts/**/*"], + "exclude": ["node_modules"] + } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0f9ebf6d..ac2a9f68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1890,6 +1890,33 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.4.0": + version: 1.4.0 + resolution: "@noble/curves@npm:1.4.0" + dependencies: + "@noble/hashes": 1.4.0 + checksum: 0014ff561d16e98da4a57e2310a4015e4bdab3b1e1eafcd18d3f9b955c29c3501452ca5d702fddf8ca92d570bbeadfbe53fe16ebbd81a319c414f739154bb26b + languageName: node + linkType: hard + +"@noble/curves@npm:^1.4.0": + version: 1.6.0 + resolution: "@noble/curves@npm:1.6.0" + dependencies: + "@noble/hashes": 1.5.0 + checksum: 258f3feb2a6098cf35521562ecb7d452fd728e8a008ff9f1ef435184f9d0c782ceb8f7b7fa8df3317c3be7a19f53995ee124cd05c8080b130bd42e3cb072f24d + languageName: node + linkType: hard + +"@noble/curves@npm:~1.4.0": + version: 1.4.2 + resolution: "@noble/curves@npm:1.4.2" + dependencies: + "@noble/hashes": 1.4.0 + checksum: c475a83c4263e2c970eaba728895b9b5d67e0ca880651e9c6e3efdc5f6a4f07ceb5b043bf71c399fc80fada0b8706e69d0772bffdd7b9de2483b988973a34cba + languageName: node + linkType: hard + "@noble/hashes@npm:1.2.0, @noble/hashes@npm:~1.2.0": version: 1.2.0 resolution: "@noble/hashes@npm:1.2.0" @@ -1911,13 +1938,20 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0": +"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" checksum: 8ba816ae26c90764b8c42493eea383716396096c5f7ba6bea559993194f49d80a73c081f315f4c367e51bd2d5891700bcdfa816b421d24ab45b41cb03e4f3342 languageName: node linkType: hard +"@noble/hashes@npm:1.5.0, @noble/hashes@npm:~1.5.0": + version: 1.5.0 + resolution: "@noble/hashes@npm:1.5.0" + checksum: 9cc031d5c888c455bfeef76af649b87f75380a4511405baea633c1e4912fd84aff7b61e99716f0231d244c9cfeda1fafd7d718963e6a0c674ed705e9b1b4f76b + languageName: node + linkType: hard + "@noble/secp256k1@npm:1.7.1, @noble/secp256k1@npm:~1.7.0": version: 1.7.1 resolution: "@noble/secp256k1@npm:1.7.1" @@ -2817,6 +2851,13 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:~1.1.6, @scure/base@npm:~1.1.8": + version: 1.1.9 + resolution: "@scure/base@npm:1.1.9" + checksum: 120820a37dfe9dfe4cab2b7b7460552d08e67dee8057ed5354eb68d8e3440890ae983ce3bee957d2b45684950b454a2b6d71d5ee77c1fd3fddc022e2a510337f + languageName: node + linkType: hard + "@scure/bip32@npm:1.1.5": version: 1.1.5 resolution: "@scure/bip32@npm:1.1.5" @@ -2850,6 +2891,17 @@ __metadata: languageName: node linkType: hard +"@scure/bip32@npm:1.4.0": + version: 1.4.0 + resolution: "@scure/bip32@npm:1.4.0" + dependencies: + "@noble/curves": ~1.4.0 + "@noble/hashes": ~1.4.0 + "@scure/base": ~1.1.6 + checksum: eff491651cbf2bea8784936de75af5fc020fc1bbb9bcb26b2cfeefbd1fb2440ebfaf30c0733ca11c0ae1e272a2ef4c3c34ba5c9fb3e1091c3285a4272045b0c6 + languageName: node + linkType: hard + "@scure/bip39@npm:1.1.1": version: 1.1.1 resolution: "@scure/bip39@npm:1.1.1" @@ -2880,6 +2932,16 @@ __metadata: languageName: node linkType: hard +"@scure/bip39@npm:1.4.0": + version: 1.4.0 + resolution: "@scure/bip39@npm:1.4.0" + dependencies: + "@noble/hashes": ~1.5.0 + "@scure/base": ~1.1.8 + checksum: 211f2c01361993bfe54c0e4949f290224381457c7f76d7cd51d6a983f3f4b6b9f85adfd0e623977d777ed80417a5fe729eb19dd34e657147810a0e58a8e7b9e0 + languageName: node + linkType: hard + "@se-2/hardhat@workspace:packages/hardhat": version: 0.0.0-use.local resolution: "@se-2/hardhat@workspace:packages/hardhat" @@ -2904,6 +2966,7 @@ __metadata: "@types/mocha": ">=9.1.0" "@typescript-eslint/eslint-plugin": latest "@typescript-eslint/parser": latest + "@wagmi/core": ^2.13.8 eslint: ^8.26.0 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 @@ -2917,6 +2980,7 @@ __metadata: ts-node: ^10.9.2 typechain: ^8.3.0 typescript: ^5.4.5 + viem: 2.x languageName: unknown linkType: soft @@ -3767,6 +3831,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.7.4": + version: 22.7.4 + resolution: "@types/node@npm:22.7.4" + dependencies: + undici-types: ~6.19.2 + checksum: a3f4154147639369aed08fe6f8d62eff637cf87b187bb252d7bbccdc82884626007af424b08a653c53f2182adfa0340001b4888cb7cbb942cef351210fc742a5 + languageName: node + linkType: hard + "@types/node@npm:^8.0.0": version: 8.10.66 resolution: "@types/node@npm:8.10.66" @@ -4517,6 +4590,26 @@ __metadata: languageName: node linkType: hard +"@wagmi/core@npm:^2.13.8": + version: 2.13.8 + resolution: "@wagmi/core@npm:2.13.8" + dependencies: + eventemitter3: 5.0.1 + mipd: 0.0.7 + zustand: 4.4.1 + peerDependencies: + "@tanstack/query-core": ">=5.0.0" + typescript: ">=5.0.4" + viem: 2.x + peerDependenciesMeta: + "@tanstack/query-core": + optional: true + typescript: + optional: true + checksum: be2f2108e22989794e94c2c41998a0389dbd9c69202400986a35d95618d97d602ee8d065b519fb5adcc5338f16091ba6ad6632e8f8c3fcbb058b3be8fdec851b + languageName: node + linkType: hard + "@walletconnect/core@npm:2.10.6": version: 2.10.6 resolution: "@walletconnect/core@npm:2.10.6" @@ -5102,6 +5195,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:1.0.5": + version: 1.0.5 + resolution: "abitype@npm:1.0.5" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: 4a4865926e5e8e33e4fab0081a106ce4f627db30b4052fbc449e4707aea6d34d805d46c8d6d0a72234bdd9a2b4900993591515fc299bc57d393181c70dc0c19e + languageName: node + linkType: hard + "abort-controller@npm:^3.0.0": version: 3.0.0 resolution: "abort-controller@npm:3.0.0" @@ -7667,6 +7775,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.4.5": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 301a12c3d44fd49888b74eb9ccf9f07a1f5df43f489e7fcb89647a2edcd84c42d6bc349dc8df099cd18f07c35c7b04685c1a4f3e6a6a9e6b30f8d48c15b7f49c + languageName: node + linkType: hard + "download@npm:^6.2.2": version: 6.2.5 resolution: "download@npm:6.2.5" @@ -8910,6 +9025,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:5.0.1, eventemitter3@npm:^5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 543d6c858ab699303c3c32e0f0f47fc64d360bf73c3daf0ac0b5079710e340d6fe9f15487f94e66c629f5f82cd1a8678d692f3dbb6f6fcd1190e1b97fcad36f8 + languageName: node + linkType: hard + "eventemitter3@npm:^4.0.7": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" @@ -8917,13 +9039,6 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^5.0.1": - version: 5.0.1 - resolution: "eventemitter3@npm:5.0.1" - checksum: 543d6c858ab699303c3c32e0f0f47fc64d360bf73c3daf0ac0b5079710e340d6fe9f15487f94e66c629f5f82cd1a8678d692f3dbb6f6fcd1190e1b97fcad36f8 - languageName: node - linkType: hard - "events-intercept@npm:^2.0.0": version: 2.0.0 resolution: "events-intercept@npm:2.0.0" @@ -11403,6 +11518,22 @@ __metadata: languageName: node linkType: hard +"isows@npm:1.0.4": + version: 1.0.4 + resolution: "isows@npm:1.0.4" + peerDependencies: + ws: "*" + checksum: a3ee62e3d6216abb3adeeb2a551fe2e7835eac87b05a6ecc3e7739259bf5f8e83290501f49e26137390c8093f207fc3378d4a7653aab76ad7bbab4b2dba9c5b9 + languageName: node + linkType: hard + +"isstream@npm:~0.1.2": + version: 0.1.2 + resolution: "isstream@npm:0.1.2" + checksum: 1eb2fe63a729f7bdd8a559ab552c69055f4f48eb5c2f03724430587c6f450783c8f1cd936c1c952d0a927925180fcc892ebd5b174236cf1065d4bd5bdb37e963 + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" @@ -13197,6 +13328,18 @@ __metadata: languageName: node linkType: hard +"mipd@npm:0.0.7": + version: 0.0.7 + resolution: "mipd@npm:0.0.7" + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: 14526f78d6d1bc8580898922508d64714f5abc7293b5998fe93c54237fd1cea120dc98674fe2b329ba3803bda5a85f3e442c3b1fa880e4c6b443bf73018514a8 + languageName: node + linkType: hard + "mkdirp@npm:0.5.x, mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.5": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" @@ -15645,10 +15788,16 @@ __metadata: version: 0.0.0-use.local resolution: "se-2@workspace:." dependencies: + "@types/node": ^22.7.4 + "@wagmi/core": ^2.13.8 axios: ^1.4.0 + dotenv: ^16.4.5 form-data: ^4.0.0 husky: ^8.0.1 lint-staged: ^13.0.3 + ts-node: latest + typescript: ^5.6.2 + viem: 2.x languageName: unknown linkType: soft @@ -17104,7 +17253,7 @@ __metadata: languageName: node linkType: hard -"ts-node@npm:^10.9.1, ts-node@npm:^10.9.2": +"ts-node@npm:^10.9.1, ts-node@npm:^10.9.2, ts-node@npm:latest": version: 10.9.2 resolution: "ts-node@npm:10.9.2" dependencies: @@ -17393,6 +17542,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.6.2": + version: 5.6.2 + resolution: "typescript@npm:5.6.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 48777e1dabd9044519f56cd012b0296e3b72bafe12b7e8e34222751d45c67e0eba5387ecdaa6c14a53871a29361127798df6dc8d1d35643a0a47cb0b1c65a33a + languageName: node + linkType: hard + "typescript@patch:typescript@4.9.5#~builtin": version: 4.9.5 resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=a1c5e5" @@ -17413,6 +17572,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@^5.6.2#~builtin": + version: 5.6.2 + resolution: "typescript@patch:typescript@npm%3A5.6.2#~builtin::version=5.6.2&hash=a1c5e5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: c084ee1ab865f108c787e6233a5f63c126c482c0c8e87ec998ac5288a2ad54b603e1ea8b8b272355823b833eb31b9fabb99e8c6152283e1cb47e3a76bd6faf6c + languageName: node + linkType: hard + "typical@npm:^4.0.0": version: 4.0.0 resolution: "typical@npm:4.0.0" @@ -17515,6 +17684,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.19.2": + version: 6.19.8 + resolution: "undici-types@npm:6.19.8" + checksum: de51f1b447d22571cf155dfe14ff6d12c5bdaec237c765085b439c38ca8518fc360e88c70f99469162bf2e14188a7b0bcb06e1ed2dc031042b984b0bb9544017 + languageName: node + linkType: hard + "undici@npm:5.26.5": version: 5.26.5 resolution: "undici@npm:5.26.5" @@ -17916,6 +18092,28 @@ __metadata: languageName: node linkType: hard +"viem@npm:2.x": + version: 2.21.18 + resolution: "viem@npm:2.21.18" + dependencies: + "@adraffy/ens-normalize": 1.10.0 + "@noble/curves": 1.4.0 + "@noble/hashes": 1.4.0 + "@scure/bip32": 1.4.0 + "@scure/bip39": 1.4.0 + abitype: 1.0.5 + isows: 1.0.4 + webauthn-p256: 0.0.5 + ws: 8.17.1 + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: 60112b796beaa1b3a2dd2f559e61fddc0afb99dc97de2a3545e0da871044ba29d1dddc877220c77332bca36d5233b2807992abfd92154c84926abbcdffd6db6d + languageName: node + linkType: hard + "viem@npm:^1.0.0, viem@npm:^1.6.0": version: 1.21.4 resolution: "viem@npm:1.21.4" @@ -18031,6 +18229,16 @@ __metadata: languageName: node linkType: hard +"webauthn-p256@npm:0.0.5": + version: 0.0.5 + resolution: "webauthn-p256@npm:0.0.5" + dependencies: + "@noble/curves": ^1.4.0 + "@noble/hashes": ^1.4.0 + checksum: 2837188d1e6d947c87c5728374fb6aec96387cb766f78e7a04d5903774264feb278d68a639748f09997f591e5278796c662bb5c4e8b8286b0f22254694023584 + languageName: node + linkType: hard + "webcrypto-core@npm:^1.8.0": version: 1.8.0 resolution: "webcrypto-core@npm:1.8.0" @@ -18295,6 +18503,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.17.1": + version: 8.17.1 + resolution: "ws@npm:8.17.1" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 442badcce1f1178ec87a0b5372ae2e9771e07c4929a3180321901f226127f252441e8689d765aa5cfba5f50ac60dd830954afc5aeae81609aefa11d3ddf5cecf + languageName: node + linkType: hard + "ws@npm:8.5.0": version: 8.5.0 resolution: "ws@npm:8.5.0" @@ -18543,6 +18766,26 @@ __metadata: languageName: node linkType: hard +"zustand@npm:4.4.1": + version: 4.4.1 + resolution: "zustand@npm:4.4.1" + dependencies: + use-sync-external-store: 1.2.0 + peerDependencies: + "@types/react": ">=16.8" + immer: ">=9.0" + react: ">=16.8" + peerDependenciesMeta: + "@types/react": + optional: true + immer: + optional: true + react: + optional: true + checksum: 80acd0fbf633782996642802c8692bbb80ae5c80a8dff4c501b88250acd5ccd468fbc6398bdce198475a25e3839c91385b81da921274f33ffb5c2d08c3eab400 + languageName: node + linkType: hard + "zustand@npm:^4.1.2, zustand@npm:^4.3.1": version: 4.5.2 resolution: "zustand@npm:4.5.2" diff --git a/zksync-ts/.env.example b/zksync-ts/.env.example new file mode 100644 index 00000000..86ec1c5c --- /dev/null +++ b/zksync-ts/.env.example @@ -0,0 +1,6 @@ +WALLET_PRIVATE_KEY= +NETWORK_CONFIG_PROXY_ADMIN= +NETWORK_CONFIG_FEE_RECIPIENT= +NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_ADDRESS= +NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT=36000 +NETWORK_CONFIG_ACCESS_AUTHORITY_ADDRESS= \ No newline at end of file diff --git a/zksync-ts/.gitignore b/zksync-ts/.gitignore new file mode 100644 index 00000000..6144bb56 --- /dev/null +++ b/zksync-ts/.gitignore @@ -0,0 +1,113 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.vscode + +# hardhat artifacts +artifacts +cache + +# zksync artifacts +artifacts-zk +cache-zk + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port diff --git a/zksync-ts/.npmrc b/zksync-ts/.npmrc new file mode 100644 index 00000000..521a9f7c --- /dev/null +++ b/zksync-ts/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/zksync-ts/LICENSE b/zksync-ts/LICENSE new file mode 100644 index 00000000..7fff3d4e --- /dev/null +++ b/zksync-ts/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Matter Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/zksync-ts/README.md b/zksync-ts/README.md new file mode 100644 index 00000000..a250e185 --- /dev/null +++ b/zksync-ts/README.md @@ -0,0 +1,51 @@ +# ZKsync Hardhat project template + +This project was scaffolded with [zksync-cli](https://github.com/matter-labs/zksync-cli). + +## Project Layout + +- `/contracts`: Contains solidity smart contracts. +- `/deploy`: Scripts for contract deployment and interaction. +- `/test`: Test files. +- `hardhat.config.ts`: Configuration settings. + +## How to Use + +- `npm run compile`: Compiles contracts. +- `npm run deploy`: Deploys using script `/deploy/deploy.ts`. +- `npm run interact`: Interacts with the deployed contract using `/deploy/interact.ts`. +- `npm run test`: Tests the contracts. + +Note: Both `npm run deploy` and `npm run interact` are set in the `package.json`. You can also run your files directly, for example: `npx hardhat deploy-zksync --script deploy.ts` + +### Environment Settings + +To keep private keys safe, this project pulls in environment variables from `.env` files. Primarily, it fetches the wallet's private key. + +Rename `.env.example` to `.env` and fill in your private key: + +``` +WALLET_PRIVATE_KEY=your_private_key_here... +``` + +### Network Support + +`hardhat.config.ts` comes with a list of networks to deploy and test contracts. Add more by adjusting the `networks` section in the `hardhat.config.ts`. To make a network the default, set the `defaultNetwork` to its name. You can also override the default using the `--network` option, like: `hardhat test --network dockerizedNode`. + +### Local Tests + +Running `npm run test` by default runs the [ZKsync In-memory Node](https://docs.zksync.io/build/test-and-debug/in-memory-node) provided by the [@matterlabs/hardhat-zksync-node](https://docs.zksync.io/build/tooling/hardhat/hardhat-zksync-node) tool. + +Important: ZKsync In-memory Node currently supports only the L2 node. If contracts also need L1, use another testing environment like Dockerized Node. Refer to [test documentation](https://docs.zksync.io/build/test-and-debug) for details. + +## Useful Links + +- [Docs](https://docs.zksync.io/build) +- [Official Site](https://zksync.io/) +- [GitHub](https://github.com/matter-labs) +- [Twitter](https://twitter.com/zksync) +- [Discord](https://join.zksync.dev/) + +## License + +This project is under the [MIT](./LICENSE) license. \ No newline at end of file diff --git a/zksync-ts/contracts/Proxies.sol b/zksync-ts/contracts/Proxies.sol new file mode 100644 index 00000000..da7e1f94 --- /dev/null +++ b/zksync-ts/contracts/Proxies.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +// We import these here to force Hardhat to compile them. +// This ensures that their artifacts are available for Hardhat Ignition to use. +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/zksync-ts/contracts/claim/BasicDistributor.sol b/zksync-ts/contracts/claim/BasicDistributor.sol new file mode 100644 index 00000000..7163c3ca --- /dev/null +++ b/zksync-ts/contracts/claim/BasicDistributor.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { AdvancedDistributor, IERC20 } from "../claim/abstract/AdvancedDistributor.sol"; + +contract BasicDistributor is AdvancedDistributor { + // The practical limit for this distributor is gas: distributing to 250 addresses costs about 7,000,000 gas! + constructor( + IERC20 _token, // the purchased token + uint256 _total, // total claimable + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _voteFactor, // voting power multiplier as fraction of fractionDenominator + address[] memory _recipients, + uint256[] memory _amounts + ) AdvancedDistributor(_token, _total, _uri, _voteFactor, 10000, 0, uint160(uint256(blockhash(block.number - 1)))) { + require(_recipients.length == _amounts.length, "_recipients, _amounts different lengths"); + uint256 _t; + for (uint256 i = _recipients.length; i != 0; ) { + unchecked { + --i; + } + + _initializeDistributionRecord(_recipients[i], _amounts[i]); + _t += _amounts[i]; + } + require(_total == _t, "sum(_amounts) != _total"); + } + + function getVestedFraction( + address, /*beneficiary*/ + uint256 /*time*/ + ) public view override returns (uint256) { + // all tokens vest immediately + return fractionDenominator; + } + + function NAME() external pure virtual override returns (string memory) { + return "BasicDistributor"; + } + + function VERSION() external pure virtual override returns (uint256) { + return 5; + } + + function claim(address beneficiary) external nonReentrant { + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, records[beneficiary].total); + // interactions + super._settleClaim(beneficiary, claimedAmount); + } +} diff --git a/zksync-ts/contracts/claim/ContinuousVestingMerkle.sol b/zksync-ts/contracts/claim/ContinuousVestingMerkle.sol new file mode 100644 index 00000000..2269766e --- /dev/null +++ b/zksync-ts/contracts/claim/ContinuousVestingMerkle.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import { ContinuousVesting } from './abstract/ContinuousVesting.sol'; +import { MerkleSet } from './abstract/MerkleSet.sol'; + +contract ContinuousVestingMerkle is ContinuousVesting, MerkleSet { + constructor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _voteFactor, // votes have this weight + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime // the maximum delay time for the fair queue + ) + ContinuousVesting( + _token, + _total, + _uri, + _voteFactor, + _start, + _cliff, + _end, + _maxDelayTime, + uint160(uint256(_merkleRoot)) + ) + MerkleSet(_merkleRoot) + {} + + function NAME() external pure override returns (string memory) { + return 'ContinuousVestingMerkle'; + } + + function VERSION() external pure override returns (uint256) { + return 3; + } + + function initializeDistributionRecord( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, amount)), merkleProof) + { + _initializeDistributionRecord(beneficiary, amount); + } + + function claim( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount)), merkleProof) + nonReentrant + { + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount); + // interactions + super._settleClaim(beneficiary, claimedAmount); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } +} diff --git a/zksync-ts/contracts/claim/CrosschainContinuousVestingMerkle.sol b/zksync-ts/contracts/claim/CrosschainContinuousVestingMerkle.sol new file mode 100644 index 00000000..23dbced4 --- /dev/null +++ b/zksync-ts/contracts/claim/CrosschainContinuousVestingMerkle.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import { CrosschainMerkleDistributor } from './abstract/CrosschainMerkleDistributor.sol'; +import { CrosschainDistributor } from './abstract/CrosschainDistributor.sol'; +import { ContinuousVesting } from './abstract/ContinuousVesting.sol'; +import { Distributor } from './abstract/Distributor.sol'; +import { AdvancedDistributor } from './abstract/AdvancedDistributor.sol'; +import { IConnext } from '../interfaces/IConnext.sol'; +import { IDistributor } from '../interfaces/IDistributor.sol'; +import { ECDSA } from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol'; + +/** + * @title CrosschainContinuousVestingMerkle + * @author + * @notice Distributes funds to beneficiaries across Connext domains and vesting continuously between a start and end date. + */ +contract CrosschainContinuousVestingMerkle is CrosschainMerkleDistributor, ContinuousVesting { + constructor( + IERC20 _token, + IConnext _connext, + uint256 _total, + string memory _uri, + uint256 _voteFactor, + uint256 _start, + uint256 _cliff, + uint256 _end, + bytes32 _merkleRoot, + uint160 _maxDelayTime // the maximum delay time for the fair queue + ) + CrosschainMerkleDistributor(_connext, _merkleRoot, _total) + ContinuousVesting( + _token, + _total, + _uri, + _voteFactor, + _start, + _cliff, + _end, + _maxDelayTime, + uint160(uint256(_merkleRoot)) + ) + {} + + // every distributor must provide a name method + function NAME() external pure override(Distributor, IDistributor) returns (string memory) { + return 'CrosschainContinuousVestingMerkle'; + } + + // every distributor must provide a version method + function VERSION() external pure override(Distributor, IDistributor) returns (uint256) { + return 1; + } + + function _setToken(IERC20 _token) internal override(AdvancedDistributor, CrosschainDistributor) { + super._setToken(_token); + } + + function _setTotal(uint256 _total) internal override(AdvancedDistributor, CrosschainDistributor) { + super._setTotal(_total); + } +} diff --git a/zksync-ts/contracts/claim/CrosschainTrancheVestingMerkle.sol b/zksync-ts/contracts/claim/CrosschainTrancheVestingMerkle.sol new file mode 100644 index 00000000..4ced8d8a --- /dev/null +++ b/zksync-ts/contracts/claim/CrosschainTrancheVestingMerkle.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import { CrosschainMerkleDistributor, CrosschainDistributor } from './abstract/CrosschainMerkleDistributor.sol'; +import { TrancheVesting, Tranche } from './abstract/TrancheVesting.sol'; +import { Distributor } from './abstract/Distributor.sol'; +import { AdvancedDistributor } from './abstract/AdvancedDistributor.sol'; +import { IConnext } from '../interfaces/IConnext.sol'; +import { IDistributor } from '../interfaces/IDistributor.sol'; + +/** + * @title CrosschainTrancheVestingMerkle + * @author + * @notice Distributes funds to beneficiaries across Connext domains and vesting in tranches over time. + */ +contract CrosschainTrancheVestingMerkle is CrosschainMerkleDistributor, TrancheVesting { + constructor( + IERC20 _token, + IConnext _connext, + uint256 _total, + string memory _uri, + uint256 _voteFactor, + Tranche[] memory _tranches, + bytes32 _merkleRoot, + uint160 _maxDelayTime // the maximum delay time for the fair queue + ) + CrosschainMerkleDistributor(_connext, _merkleRoot, _total) + TrancheVesting(_token, _total, _uri, _voteFactor, _tranches, _maxDelayTime, uint160(uint256(_merkleRoot))) + {} + + // Every distributor must provide a name method + function NAME() external pure override(Distributor, IDistributor) returns (string memory) { + return 'CrosschainTrancheVestingMerkle'; + } + + // Every distributor must provide a version method to track changes + function VERSION() external pure override(Distributor, IDistributor) returns (uint256) { + return 1; + } + + function _setToken(IERC20 _token) internal override(AdvancedDistributor, CrosschainDistributor) { + super._setToken(_token); + } + + function _setTotal(uint256 _total) internal override(AdvancedDistributor, CrosschainDistributor) { + super._setTotal(_total); + } +} diff --git a/zksync-ts/contracts/claim/FeeLevelJudgeStub.sol b/zksync-ts/contracts/claim/FeeLevelJudgeStub.sol new file mode 100644 index 00000000..bd00c9f5 --- /dev/null +++ b/zksync-ts/contracts/claim/FeeLevelJudgeStub.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IFeeLevelJudge.sol"; + +contract FeeLevelJudgeStub is IFeeLevelJudge { + uint256 feeLevel; + + constructor(uint256 _feeLevel) { + feeLevel = _feeLevel; + } + + function getFeeLevel(address) external view returns (uint256) { + return feeLevel; + } +} diff --git a/zksync-ts/contracts/claim/IFeeLevelJudge.sol b/zksync-ts/contracts/claim/IFeeLevelJudge.sol new file mode 100644 index 00000000..52c936f4 --- /dev/null +++ b/zksync-ts/contracts/claim/IFeeLevelJudge.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +interface IFeeLevelJudge { + function getFeeLevel(address user) external view returns (uint256); +} diff --git a/zksync-ts/contracts/claim/PriceTierVestingMerkle.sol b/zksync-ts/contracts/claim/PriceTierVestingMerkle.sol new file mode 100644 index 00000000..5df561cc --- /dev/null +++ b/zksync-ts/contracts/claim/PriceTierVestingMerkle.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import { PriceTierVesting, PriceTier } from './abstract/PriceTierVesting.sol'; +import { MerkleSet } from './abstract/MerkleSet.sol'; +import "../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; + +contract PriceTierVestingMerkle is PriceTierVesting, MerkleSet { + constructor( + IERC20 _token, + uint256 _total, + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _voteFactor, + // when price tier vesting opens (seconds past epoch) + uint256 _start, + // when price tier vesting ends (seconds past epoch) and all tokens are unlocked + uint256 _end, + // source for pricing info + IOracleOrL2OracleWithSequencerCheck _oracle, + PriceTier[] memory _priceTiers, + bytes32 _merkleRoot, + uint160 _maxDelayTime // the maximum delay time for the fair queue + ) + PriceTierVesting( + _token, + _total, + _uri, + _voteFactor, + _start, + _end, + _oracle, + _priceTiers, + _maxDelayTime, + uint160(uint256(_merkleRoot)) + ) + MerkleSet(_merkleRoot) + {} + + function NAME() external pure override returns (string memory) { + return 'PriceTierVestingMerkle'; + } + + function VERSION() external pure override returns (uint256) { + return 3; + } + + function initializeDistributionRecord( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, amount)), merkleProof) + { + _initializeDistributionRecord(beneficiary, amount); + } + + function claim( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount)), merkleProof) + nonReentrant + { + // effects + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } +} diff --git a/zksync-ts/contracts/claim/PriceTierVestingSale_2_0.sol b/zksync-ts/contracts/claim/PriceTierVestingSale_2_0.sol new file mode 100644 index 00000000..f0262eb5 --- /dev/null +++ b/zksync-ts/contracts/claim/PriceTierVestingSale_2_0.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; + +import { DistributionRecord } from '../interfaces/IDistributor.sol'; +import { PriceTierVesting, PriceTier } from './abstract/PriceTierVesting.sol'; +import { MerkleSet } from './abstract/MerkleSet.sol'; +import { FlatPriceSale } from '../sale/v2/FlatPriceSale.sol'; +import "../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; + +contract PriceTierVestingSale_2_0 is PriceTierVesting { + FlatPriceSale public immutable sale; + uint256 public immutable price; + uint8 public immutable soldTokenDecimals; + + modifier validSaleParticipant(address beneficiary) { + require(sale.buyerTotal(beneficiary) != 0, 'no purchases found'); + + _; + } + + constructor( + FlatPriceSale _sale, // where the purchase occurred + IERC20 _token, // the purchased token + uint8 _soldTokenDecimals, // the number of decimals used by the purchased token + // the price of the purchased token denominated in the sale's base currency with 8 decimals + // e.g. if the sale was selling $FOO at $0.55 per token, price = 55000000 + uint256 _price, + // when price tier vesting opens (seconds past epoch) + uint256 _start, + // when price tier vesting ends (seconds past epoch) and all tokens are unlocked + uint256 _end, + // source for pricing info + IOracleOrL2OracleWithSequencerCheck _oracle, + PriceTier[] memory priceTiers, // vesting PriceTiers + uint256 _voteFactor, // the factor for voting power in basis points (e.g. 15000 means users have a 50% voting bonus for unclaimed tokens) + string memory _uri // information on the sale (e.g. merkle proofs) + ) + PriceTierVesting( + _token, + (_sale.total() * 10 ** _soldTokenDecimals) / _price, + _uri, + _voteFactor, + _start, + _end, + _oracle, + priceTiers, + 0, // no delay + 0 // no salt + ) + { + require(address(_sale) != address(0), 'sale is address(0)'); + + // previously deployed v2.0 sales did not implement the isOver() method + (, , , , , , uint256 endTime, , ) = _sale.config(); + require(endTime < block.timestamp, 'sale not over yet'); + require(_price != 0, 'price is 0'); + + sale = _sale; + soldTokenDecimals = _soldTokenDecimals; + price = _price; + } + + function NAME() external pure virtual override returns (string memory) { + return 'PriceTierVestingSale_2_0'; + } + + // File specific version - starts at 1, increments on every solidity diff + function VERSION() external pure virtual override returns (uint256) { + return 3; + } + + function getPurchasedAmount(address buyer) public view returns (uint256) { + /** + Get the quantity purchased from the sale and convert it to native tokens + + Example: if a user buys $1.11 of a FOO token worth $0.50 each, the purchased amount will be 2.22 FOO + - buyer total: 111000000 ($1.11 with 8 decimals) + - decimals: 6 (the token being purchased has 6 decimals) + - price: 50000000 ($0.50 with 8 decimals) + + Calculation: 111000000 * 1000000 / 50000000 + + Returns purchased amount: 2220000 (2.22 with 6 decimals) + */ + return (sale.buyerTotal(buyer) * (10 ** soldTokenDecimals)) / price; + } + + function initializeDistributionRecord( + address beneficiary // the address that will receive tokens + ) external validSaleParticipant(beneficiary) { + _initializeDistributionRecord(beneficiary, getPurchasedAmount(beneficiary)); + } + + function claim( + address beneficiary // the address that will receive tokens + ) external validSaleParticipant(beneficiary) nonReentrant { + uint256 totalClaimableAmount = getTotalClaimableAmount(beneficiary); + + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount); + + // interactions + super._settleClaim(beneficiary, claimedAmount); + } + + function getDistributionRecord( + address beneficiary + ) external view virtual override returns (DistributionRecord memory) { + DistributionRecord memory record = records[beneficiary]; + + // workaround prior to initialization + if (!record.initialized) { + record.total = uint120(getPurchasedAmount(beneficiary)); + } + return record; + } + + // get the number of tokens currently claimable by a specific user + function getClaimableAmount(address beneficiary) public view override returns (uint256) { + if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary); + + // we can get the claimable amount prior to initialization + return + (getPurchasedAmount(beneficiary) * getVestedFraction(beneficiary, block.timestamp)) / + fractionDenominator; + } + + // get the total number of tokens claimable regardless of vesting + function getTotalClaimableAmount(address beneficiary) internal view returns (uint256) { + // check the distribution record first, if the user's claimable + // amount was adjusted, it will be initialized/total updated + if (records[beneficiary].initialized) return records[beneficiary].total; + + return getPurchasedAmount(beneficiary); + } +} diff --git a/zksync-ts/contracts/claim/Satellite.sol b/zksync-ts/contracts/claim/Satellite.sol new file mode 100644 index 00000000..3116ef3d --- /dev/null +++ b/zksync-ts/contracts/claim/Satellite.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import '@openzeppelin/contracts/access/Ownable.sol'; + +import { IConnext } from '../interfaces/IConnext.sol'; +import { ICrosschain } from '../interfaces/ICrosschain.sol'; +import { MerkleSet } from './abstract/MerkleSet.sol'; + +/** + * @title Satellite + * @notice This contract allows a beneficiary to claim tokens to this chain from a Distributor on another chain. + * This contract validates inclusion in the merkle root, but only as a sanity check. The distributor contract + * is the source of truth for claim eligibility. + * + * @dev The beneficiary domain in the merkle leaf must be this contract's domain. The beneficiary address may be + * an EOA or a smart contract and must match msg.sender. The Satellite contract(s) and CrosschainDistributor contracts must only + * be deployed on chains supported by the Connext protocol. + * + * Note that anyone could deploy a fake Satellite contract that does not require msg.sender to match the beneficiary or the Satellite + * domain to match the beneficiary domain. This would allow the attacker to claim tokens from the distributor on behalf of a beneficiary onto + * the chain / domain specified by that beneficiary's merkle leaf. This is not a security risk to the CrosschainMerkleDistributor, + * as this is the intended behavior for a properly constructed merkle root. + */ + +contract Satellite is MerkleSet, Ownable { + // ========== Events =========== + /** + * @notice Emitted when a claim is initiated + * @param id The transfer id for sending claim to distributor + * @param beneficiary The user claiming tokens + * @param total The beneficiary's total claimable token quantity (which may not be immediately claimable due to vesting conditions) + */ + event ClaimInitiated(bytes32 indexed id, address indexed beneficiary, uint256 total); + + // ========== Storage =========== + + /** + * @notice The distributor hosted on on distributorDomain + */ + ICrosschain public immutable distributor; + + /** + * @notice The domain of the distributor + */ + uint32 public immutable distributorDomain; + + /** + * @notice The domain of this satellite + */ + uint32 public immutable domain; + + /** + * @notice Address of Connext on the satellite domain + */ + IConnext public immutable connext; + + constructor( + IConnext _connext, + ICrosschain _distributor, + uint32 _distributorDomain, + bytes32 _merkleRoot + ) MerkleSet(_merkleRoot) { + distributor = _distributor; + distributorDomain = _distributorDomain; + connext = _connext; + domain = uint32(_connext.domain()); + + // the distributor must be deployed on a different domain than the satellite + require(_distributorDomain != domain, 'same domain'); + } + + // ========== Public Methods =========== + + /** + * @notice Initiates crosschain claim by msg.sender, relayer fees paid by native asset only. + * @dev Verifies membership in distribution merkle proof and xcalls to Distributor to initiate claim + * @param total The amount of the claim (in leaf) + * @param proof The merkle proof of the leaf in the root + */ + function initiateClaim(uint256 total, bytes32[] calldata proof) public payable { + // load values into memory to reduce sloads + uint32 _distributorDomain = distributorDomain; + uint32 _domain = domain; + + // Verify the proof before sending cross-chain as a cost + time saving step + _verifyMembership(keccak256(abi.encodePacked(msg.sender, total, _domain)), proof); + + // Send claim to distributor via crosschain call + bytes32 transferId = connext.xcall{ value: msg.value }( + _distributorDomain, // destination domain + address(distributor), // to + address(0), // asset + address(0), // delegate, only required for self-execution + slippage + 0, // total + 0, // slippage + abi.encode(msg.sender, _domain, total, proof) // data + ); + + // Emit event + emit ClaimInitiated(transferId, msg.sender, total); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } +} \ No newline at end of file diff --git a/zksync-ts/contracts/claim/Staking.sol b/zksync-ts/contracts/claim/Staking.sol new file mode 100644 index 00000000..fcae8ccb --- /dev/null +++ b/zksync-ts/contracts/claim/Staking.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; + +import "./IFeeLevelJudge.sol"; + +interface ISoftMfers { + function redeem(address receiver, uint256 amount) external; +} + +contract StakingContract is Ownable2StepUpgradeable, PausableUpgradeable, IFeeLevelJudge { + IERC20 public softToken; + ISoftMfers public softMfersContract; + bool private initialized; + + struct StakedToken { + uint256 amount; + uint256 penaltyPeriodStartTime; + uint256 nftsMinted; + uint256 pendingFeeLevel; + uint256 earnedFeeLevel; + } + + mapping(address => StakedToken) public stakedTokens; + + event Staked(address indexed user, uint256 amount); + event Unstaked(address indexed user, uint256 amount); + event NFTMinted(address indexed user, uint256 numNFTs); + event FeesAdjusted(address indexed user, uint256 newFeeLevel); + event SoftTokenAddressUpdated(address indexed newAddress); + event SoftMfersAddressUpdated(address indexed newAddress); + + function initialize(address _softToken, address _softMfersAddress) public initializer { + require(!initialized, "Contract instance has already been initialized"); + initialized = true; + softToken = IERC20(_softToken); + softMfersContract = ISoftMfers(_softMfersAddress); + __Ownable2Step_init(); + } + + function updateSoftTokenAddress(address _newSoftTokenAddress) external onlyOwner { + require(_newSoftTokenAddress != address(0), "Invalid token address"); + softToken = IERC20(_newSoftTokenAddress); + emit SoftTokenAddressUpdated(_newSoftTokenAddress); + } + + function updateSoftMfersAddress(address _newSoftMfersAddress) external onlyOwner { + require(_newSoftMfersAddress != address(0), "Invalid NFT contract address"); + softMfersContract = ISoftMfers(_newSoftMfersAddress); + emit SoftMfersAddressUpdated(_newSoftMfersAddress); + } + + function getStakedAmount(address user) public view returns (uint256) { + return stakedTokens[user].amount; + } + + function getTotalFutureRedeemableNFTCount(address user) public view returns (uint256) { + uint256 amount = stakedTokens[user].amount; + + if (amount >= 10000e18) { + return 12; + } else if (amount >= 5000e18) { + return 9; + } else if (amount >= 2500e18) { + return 6; + } else if (amount >= 1000e18) { + return 3; + } + + return 0; + } + + function redeemNFTs() external { + uint256 timeElapsedSincePeriodStart = block.timestamp - stakedTokens[msg.sender].penaltyPeriodStartTime; + uint256 monthsElapsedSincePeriodStart = timeElapsedSincePeriodStart / (30 days); + uint256 totalFutureRedeemableNFTCount = getTotalFutureRedeemableNFTCount(msg.sender); + uint256 nftsMinted = stakedTokens[msg.sender].nftsMinted; + uint256 redeemableNFTCount = Math.min(monthsElapsedSincePeriodStart, totalFutureRedeemableNFTCount) - nftsMinted; + + require(redeemableNFTCount > 0, "No new NFTs to redeem"); + + stakedTokens[msg.sender].nftsMinted += redeemableNFTCount; + softMfersContract.redeem(msg.sender, redeemableNFTCount); + emit NFTMinted(msg.sender, redeemableNFTCount); + } + + function stake(uint256 amount) external whenNotPaused { + require(amount > 0, "Amount must be greater than 0"); + + if (hasPenaltyPeriodElapsed(msg.sender)) { + stakedTokens[msg.sender].earnedFeeLevel = stakedTokens[msg.sender].pendingFeeLevel; + } + + uint256 userBalance = softToken.balanceOf(msg.sender); + require(userBalance >= amount, "Insufficient token balance"); + + uint256 userAllowance = softToken.allowance(msg.sender, address(this)); + require(userAllowance >= amount, "Insufficient token allowance"); + + bool transferSuccess = softToken.transferFrom(msg.sender, address(this), amount); + require(transferSuccess, "Token transfer failed"); + + stakedTokens[msg.sender].amount += amount; + stakedTokens[msg.sender].penaltyPeriodStartTime = block.timestamp; + stakedTokens[msg.sender].pendingFeeLevel = getPendingFeeLevel(msg.sender); + + emit Staked(msg.sender, amount); + } + + function unstake(uint256 amount) external whenNotPaused { + require(stakedTokens[msg.sender].amount >= amount, "Insufficient staked tokens"); + uint256 penalty = hasPenaltyPeriodElapsed(msg.sender) ? 0 : getPenaltyAmount(msg.sender); + + uint256 withdrawAmount = amount - penalty; + stakedTokens[msg.sender].amount -= amount; + stakedTokens[msg.sender].penaltyPeriodStartTime = block.timestamp; + + stakedTokens[msg.sender].earnedFeeLevel = 100; + stakedTokens[msg.sender].pendingFeeLevel = getPendingFeeLevel(msg.sender); + + softToken.transfer(msg.sender, withdrawAmount); + emit Unstaked(msg.sender, amount); + } + + function hasPenaltyPeriodElapsed(address user) public view returns (bool) { + uint256 timeElapsedSincePeriodStart = block.timestamp - stakedTokens[user].penaltyPeriodStartTime; + uint256 penaltyPeriodDuration = getPenaltyPeriodDuration(user); + + return timeElapsedSincePeriodStart >= penaltyPeriodDuration; + } + + function getPendingFeeLevel(address user) public view returns (uint256) { + uint256 amount = stakedTokens[user].amount; + + if (amount >= 75000e18) { + return 10; + } else if (amount >= 50000e18) { + return 25; + } else if (amount >= 25000e18) { + return 50; + } else if (amount >= 10000e18) { + return 65; + } else if (amount >= 5000e18) { + return 75; + } else if (amount >= 2500e18) { + return 85; + } + + return 100; + } + + function getFeeLevel(address user) external view returns (uint256) { + if (hasPenaltyPeriodElapsed(user)) { + return stakedTokens[user].pendingFeeLevel; + } + + if (stakedTokens[user].earnedFeeLevel == 0) { + return 100; + } + + return stakedTokens[user].earnedFeeLevel; + } + + function getRedeemableNFTCount(address user) public view returns (uint256) { + uint256 timeElapsedSincePeriodStart = block.timestamp - stakedTokens[user].penaltyPeriodStartTime; + uint256 monthsElapsedSincePeriodStart = timeElapsedSincePeriodStart / (30 days); + uint256 totalFutureRedeemableNFTCount = getTotalFutureRedeemableNFTCount(user); + uint256 nftsMinted = stakedTokens[user].nftsMinted; + uint256 redeemableNFTCount = Math.min(monthsElapsedSincePeriodStart, totalFutureRedeemableNFTCount) - nftsMinted; + return redeemableNFTCount; + } + + function getTimeUntilNextRedeem(address user) public view returns (uint256) { + uint256 lastRedeemTime = stakedTokens[user].penaltyPeriodStartTime; + uint256 nextRedeemTime = lastRedeemTime + (30 days); + + if (block.timestamp >= nextRedeemTime) { + return 0; + } else { + return nextRedeemTime - block.timestamp; + } + } + + function getPenaltyPeriodDuration(address user) public view returns (uint256) { + uint256 amount = stakedTokens[user].amount; + + if (amount >= 75000e18) { + return 90 days; + } else if (amount >= 50000e18) { + return 60 days; + } else if (amount >= 25000e18) { + return 45 days; + } else if (amount >= 5000e18) { + return 30 days; + } else { + return 15 days; + } + } + + function getPenaltyAmount(address user) public view returns (uint256) { + uint256 amount = stakedTokens[user].amount; + + if (amount >= 50000e18) { + return (amount * 200) / 10000; // 2% + } else if (amount >= 10000e18) { + return (amount * 500) / 10000; // 5% + } + + return (amount * 1000) / 10000; // 10% + } + + function pause() external onlyOwner { + _pause(); + } + + function unpause() external onlyOwner { + _unpause(); + } +} diff --git a/zksync-ts/contracts/claim/TrancheVestingMerkle.sol b/zksync-ts/contracts/claim/TrancheVestingMerkle.sol new file mode 100644 index 00000000..90675c68 --- /dev/null +++ b/zksync-ts/contracts/claim/TrancheVestingMerkle.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import { TrancheVesting, Tranche } from './abstract/TrancheVesting.sol'; +import { MerkleSet } from './abstract/MerkleSet.sol'; + +contract TrancheVestingMerkle is TrancheVesting, MerkleSet { + constructor( + IERC20 _token, + uint256 _total, + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _voteFactor, + Tranche[] memory _tranches, + bytes32 _merkleRoot, + uint160 _maxDelayTime // the maximum delay time for the fair queue + ) + TrancheVesting( + _token, + _total, + _uri, + _voteFactor, + _tranches, + _maxDelayTime, + uint160(uint256(_merkleRoot)) + ) + MerkleSet(_merkleRoot) + {} + + function NAME() external pure override returns (string memory) { + return 'TrancheVestingMerkle'; + } + + function VERSION() external pure override returns (uint256) { + return 3; + } + + function initializeDistributionRecord( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, amount)), merkleProof) + { + _initializeDistributionRecord(beneficiary, amount); + } + + function claim( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount)), merkleProof) + nonReentrant + { + // effects + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } +} diff --git a/zksync-ts/contracts/claim/TrancheVestingSale_1_3.sol b/zksync-ts/contracts/claim/TrancheVestingSale_1_3.sol new file mode 100644 index 00000000..4768548a --- /dev/null +++ b/zksync-ts/contracts/claim/TrancheVestingSale_1_3.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol'; + +import { DistributionRecord } from './abstract/Distributor.sol'; +import { TrancheVesting, Tranche } from './abstract/TrancheVesting.sol'; +import { MerkleSet } from './abstract/MerkleSet.sol'; +import { ISaleManager_v_1_3 } from '../sale/v1.3/ISaleManager.sol'; + +contract TrancheVestingSale_1_3 is TrancheVesting { + ISaleManager_v_1_3 public immutable saleManager; + bytes32 public immutable saleId; + + modifier validSaleParticipant(address beneficiary) { + require(saleManager.getSpent(saleId, beneficiary) != 0, 'no purchases found'); + + _; + } + + constructor( + ISaleManager_v_1_3 _saleManager, // where the purchase occurred + bytes32 _saleId, // the sale id + IERC20 _token, // the purchased token to distribute + Tranche[] memory _tranches, // vesting tranches + uint256 _voteFactor, // the factor for voting power (e.g. 15000 means users have a 50% voting bonus for unclaimed tokens) + string memory _uri // information on the sale (e.g. merkle proofs) + ) + TrancheVesting( + _token, + _saleManager.spentToBought(_saleId, _saleManager.getTotalSpent(_saleId)), + _uri, + _voteFactor, + _tranches, + 0, // no delay + 0 // no salt + ) + { + require(address(_saleManager) != address(0), 'TVS_1_3_D: sale is address(0)'); + require(_saleId != bytes32(0), 'TVS_1_3_D: sale id is bytes(0)'); + + // if the ERC20 token provides decimals, ensure they match + int256 decimals = tryDecimals(_token); + require( + decimals == -1 || decimals == int256(_saleManager.getDecimals(_saleId)), + 'token decimals do not match sale' + ); + require(_saleManager.isOver(_saleId), 'TVS_1_3_D: sale not over'); + + saleManager = _saleManager; + saleId = _saleId; + } + + function NAME() external pure virtual override returns (string memory) { + return 'TrancheVestingSale_1_3'; + } + + // File specific version - starts at 1, increments on every solidity diff + function VERSION() external pure virtual override returns (uint256) { + return 4; + } + + function tryDecimals(IERC20 _token) internal view returns (int256) { + try IERC20Metadata(address(_token)).decimals() returns (uint8 decimals) { + return int256(uint256(decimals)); + } catch { + return -1; + } + } + + function getPurchasedAmount(address buyer) public view returns (uint256) { + /** + Get the purchased token quantity from the sale + + Example: if a user buys $1.11 of a FOO token worth $0.50 each, the purchased amount will be 2.22 FOO + Returns purchased amount: 2220000 (2.22 with 6 decimals) + */ + return saleManager.getBought(saleId, buyer); + } + + function initializeDistributionRecord( + address beneficiary // the address that will receive tokens + ) external validSaleParticipant(beneficiary) { + _initializeDistributionRecord(beneficiary, getPurchasedAmount(beneficiary)); + } + + function claim( + address beneficiary // the address that will receive tokens + ) external validSaleParticipant(beneficiary) nonReentrant { + uint256 totalClaimableAmount = getTotalClaimableAmount(beneficiary); + + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount); + + // interactions + super._settleClaim(beneficiary, claimedAmount); + } + + function getDistributionRecord( + address beneficiary + ) external view override returns (DistributionRecord memory) { + DistributionRecord memory record = records[beneficiary]; + + // workaround prior to initialization + if (!record.initialized) { + record.total = uint120(getPurchasedAmount(beneficiary)); + } + return record; + } + + // get the number of tokens currently claimable by a specific user + function getClaimableAmount(address beneficiary) public view override returns (uint256) { + if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary); + + // we can get the claimable amount prior to initialization + return + (getPurchasedAmount(beneficiary) * getVestedFraction(beneficiary, block.timestamp)) / + fractionDenominator; + } + + // get the total number of tokens claimable regardless of vesting + function getTotalClaimableAmount(address beneficiary) internal view returns (uint256) { + // check the distribution record first, if the user's claimable + // amount was adjusted, it will be initialized/total updated + if (records[beneficiary].initialized) return records[beneficiary].total; + + return getPurchasedAmount(beneficiary); + } +} diff --git a/zksync-ts/contracts/claim/TrancheVestingSale_2_0.sol b/zksync-ts/contracts/claim/TrancheVestingSale_2_0.sol new file mode 100644 index 00000000..950bda22 --- /dev/null +++ b/zksync-ts/contracts/claim/TrancheVestingSale_2_0.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; + +import { DistributionRecord } from '../interfaces/IDistributor.sol'; +import { TrancheVesting, Tranche } from './abstract/TrancheVesting.sol'; +import { MerkleSet } from './abstract/MerkleSet.sol'; +import { FlatPriceSale } from '../sale/v2/FlatPriceSale.sol'; + +contract TrancheVestingSale_2_0 is TrancheVesting { + FlatPriceSale public immutable sale; + uint256 public immutable price; + uint8 public immutable soldTokenDecimals; + + modifier validSaleParticipant(address beneficiary) { + require(sale.buyerTotal(beneficiary) != 0, 'no purchases found'); + + _; + } + + constructor( + FlatPriceSale _sale, // where the purchase occurred + IERC20 _token, // the purchased token + uint8 _soldTokenDecimals, // the number of decimals used by the purchased token + // the price of the purchased token denominated in the sale's base currency with 8 decimals + // e.g. if the sale was selling $FOO at $0.55 per token, price = 55000000 + uint256 _price, + Tranche[] memory tranches, // vesting tranches + uint256 voteWeightBips, // the factor for voting power (e.g. 15000 means users have a 50% voting bonus for unclaimed tokens) + string memory _uri // information on the sale (e.g. merkle proofs) + ) + TrancheVesting( + _token, + (_sale.total() * 10 ** _soldTokenDecimals) / _price, + _uri, + voteWeightBips, + tranches, + 0, // no delay + 0 // no salt + ) + { + require(address(_sale) != address(0), 'TVS_2_0_D: sale is address(0)'); + + // previously deployed v2.0 sales did not implement the isOver() method + (, , , , , , uint256 endTime, , ) = _sale.config(); + require(endTime < block.timestamp, 'TVS_2_0_D: sale not over yet'); + require(_price != 0, 'TVS_2_0_D: price is 0'); + + sale = _sale; + soldTokenDecimals = _soldTokenDecimals; + price = _price; + } + + function NAME() external pure virtual override returns (string memory) { + return 'TrancheVestingSale_2_0'; + } + + // File specific version - starts at 1, increments on every solidity diff + function VERSION() external pure virtual override returns (uint256) { + return 5; + } + + function getPurchasedAmount(address buyer) public view returns (uint256) { + /** + Get the quantity purchased from the sale and convert it to native tokens + + Example: if a user buys $1.11 of a FOO token worth $0.50 each, the purchased amount will be 2.22 FOO + - buyer total: 111000000 ($1.11 with 8 decimals) + - decimals: 6 (the token being purchased has 6 decimals) + - price: 50000000 ($0.50 with 8 decimals) + + Calculation: 111000000 * 1000000 / 50000000 + + Returns purchased amount: 2220000 (2.22 with 6 decimals) + */ + return (sale.buyerTotal(buyer) * (10 ** soldTokenDecimals)) / price; + } + + function initializeDistributionRecord( + address beneficiary // the address that will receive tokens + ) external validSaleParticipant(beneficiary) { + _initializeDistributionRecord(beneficiary, getPurchasedAmount(beneficiary)); + } + + function claim( + address beneficiary // the address that will receive tokens + ) external validSaleParticipant(beneficiary) nonReentrant { + uint256 totalClaimableAmount = getTotalClaimableAmount(beneficiary); + + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount); + + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + function getDistributionRecord( + address beneficiary + ) external view virtual override returns (DistributionRecord memory) { + DistributionRecord memory record = records[beneficiary]; + + // workaround prior to initialization + if (!record.initialized) { + record.total = uint120(getPurchasedAmount(beneficiary)); + } + return record; + } + + // get the number of tokens currently claimable by a specific user + function getClaimableAmount(address beneficiary) public view override returns (uint256) { + if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary); + + // we can get the claimable amount prior to initialization + return + (getPurchasedAmount(beneficiary) * getVestedFraction(beneficiary, block.timestamp)) / + fractionDenominator; + } + + // get the total number of tokens claimable regardless of vesting + function getTotalClaimableAmount(address beneficiary) internal view returns (uint256) { + // check the distribution record first, if the user's claimable + // amount was adjusted, it will be initialized/total updated + if (records[beneficiary].initialized) return records[beneficiary].total; + + return getPurchasedAmount(beneficiary); + } +} diff --git a/zksync-ts/contracts/claim/abstract/AdvancedDistributor.sol b/zksync-ts/contracts/claim/abstract/AdvancedDistributor.sol new file mode 100644 index 00000000..cc7b9a81 --- /dev/null +++ b/zksync-ts/contracts/claim/abstract/AdvancedDistributor.sol @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import '@openzeppelin/contracts/access/Ownable.sol'; +import { ERC20Votes, ERC20Permit, ERC20 } from '@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol'; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; + +import { Distributor, DistributionRecord, IERC20 } from './Distributor.sol'; +import { IAdjustable } from '../../interfaces/IAdjustable.sol'; +import { IVoting } from '../../interfaces/IVoting.sol'; +import { Sweepable } from '../../utilities/Sweepable.sol'; +import { FairQueue } from '../../utilities/FairQueue.sol'; + +/** + * @title AdvancedDistributor + * @notice Distributes tokens to beneficiaries with voting-while-vesting and administrative controls. + * The contract owner can control these distribution parameters: + * - the merkle root determining all distribution details + * - adjustments to specific distributions + * - the token being distributed + * - the total amount to distribute + * - the metadata URI + * - the voting power of undistributed tokens + * - the recipient of swept funds + * + * This contract also allows owners to perform several other admin functions + * - updating the contract owner + * - sweeping tokens and native currency to a recipient + * + * This contract tracks beneficiary voting power through an internal ERC20Votes token that cannot be transferred. The + * beneficiary must delegate to an address to use this voting power. Their voting power decreases when the token is claimed. + * + * @dev Updates to the contract must follow these constraints: + * - If a merkle root update alters the total token quantity to distribute across all users, also adjust the total value. + * The DistributionRecord for each beneficiary updated in the merkle root will be incorrect until a claim is executed. + * - If the total changes, make sure to add or withdraw tokens being distributed to match the new total. + */ +abstract contract AdvancedDistributor is + Ownable, + Sweepable, + ERC20Votes, + Distributor, + IAdjustable, + IVoting, + FairQueue +{ + using SafeERC20 for IERC20; + + uint256 private voteFactor; + + constructor( + IERC20 _token, + uint256 _total, + string memory _uri, + uint256 _voteFactor, + uint256 _fractionDenominator, + uint160 _maxDelayTime, + uint160 _salt + ) + Distributor(_token, _total, _uri, _fractionDenominator) + ERC20Permit('Internal vote tracker') + ERC20('Internal vote tracker', 'IVT') + Sweepable(payable(msg.sender)) + FairQueue(_maxDelayTime, _salt) + { + voteFactor = _voteFactor; + emit SetVoteFactor(voteFactor); + } + + /** + * convert a token quantity to a vote quantity + */ + function tokensToVotes(uint256 tokenAmount) private view returns (uint256) { + return (tokenAmount * voteFactor) / fractionDenominator; + } + + // Update voting power based on distribution record initialization or claims + function _reconcileVotingPower(address beneficiary) private { + // current potential voting power + uint256 currentVotes = balanceOf(beneficiary); + // correct voting power after initialization, claim, or adjustment + DistributionRecord memory record = records[beneficiary]; + uint256 newVotes = record.claimed >= record.total ? 0 : tokensToVotes(record.total - record.claimed); + + if (currentVotes > newVotes) { + // reduce voting power through ERC20Votes extension + _burn(beneficiary, currentVotes - newVotes); + } else if (currentVotes < newVotes) { + // increase voting power through ERC20Votes extension + _mint(beneficiary, newVotes - currentVotes); + } + } + + function _initializeDistributionRecord( + address beneficiary, + uint256 totalAmount + ) internal virtual override { + super._initializeDistributionRecord(beneficiary, totalAmount); + _reconcileVotingPower(beneficiary); + } + + function _executeClaim( + address beneficiary, + uint256 totalAmount + ) internal virtual override returns (uint256 _claimed) { + _claimed = super._executeClaim(beneficiary, totalAmount); + _reconcileVotingPower(beneficiary); + } + + /** + * @dev Adjust the quantity claimable by a user, overriding the value in the distribution record. + * + * Note: If used in combination with merkle proofs, adjustments to a beneficiary's total could be + * reset by anyone to the value in the merkle leaf at any time. Update the merkle root instead. + * + * Amount is limited to type(uint120).max to allow each DistributionRecord to be packed into a single storage slot. + */ + function adjust(address beneficiary, int256 amount) external onlyOwner { + DistributionRecord memory distributionRecord = records[beneficiary]; + require(distributionRecord.initialized, 'must initialize before adjusting'); + + uint256 diff = uint256(amount > 0 ? amount : -amount); + require(diff < type(uint120).max, 'adjustment > max uint120'); + + if (amount < 0) { + // decreasing claimable tokens + require(total >= diff, 'decrease greater than distributor total'); + require(distributionRecord.total >= diff, 'decrease greater than distributionRecord total'); + total -= diff; + records[beneficiary].total -= uint120(diff); + token.safeTransfer(owner(), diff); + } else { + // increasing claimable tokens + total += diff; + records[beneficiary].total += uint120(diff); + } + _reconcileVotingPower(beneficiary); + emit Adjust(beneficiary, amount); + } + + + function _setToken(IERC20 _token) internal virtual { + require(address(_token) != address(0), 'token is address(0)'); + token = _token; + emit SetToken(token); + } + + // Set the token being distributed + function setToken(IERC20 _token) external virtual onlyOwner { + _setToken(_token); + } + + function _setTotal(uint256 _total) internal virtual { + total = _total; + emit SetTotal(total); + } + + // Set the total to distribute + function setTotal(uint256 _total) external virtual onlyOwner { + _setTotal(_total); + } + + // Set the distributor metadata URI + function setUri(string memory _uri) external onlyOwner { + uri = _uri; + emit SetUri(uri); + } + + // set the recipient of swept funds + function setSweepRecipient(address payable _recipient) external onlyOwner { + _setSweepRecipient(_recipient); + } + + function getTotalVotes() external view returns (uint256) { + // supply of internal token used to track voting power + return totalSupply(); + } + + function getVoteFactor(address) external view returns (uint256) { + return voteFactor; + } + + /** + * @notice Set the voting power of undistributed tokens + * @param _voteFactor The voting power multiplier as a fraction of fractionDenominator + * @dev The vote factor can be any integer. If voteFactor / fractionDenominator == 1, + * one unclaimed token provides one vote. If voteFactor / fractionDenominator == 2, one + * unclaimed token counts as two votes. + */ + function setVoteFactor(uint256 _voteFactor) external onlyOwner { + voteFactor = _voteFactor; + emit SetVoteFactor(voteFactor); + } + + /** + * @dev the internal token used only for tracking voting power cannot be transferred + */ + function _approve(address, address, uint256) internal pure override { + revert('disabled for voting power'); + } + + /** + * @dev the internal token used only f or tracking voting power cannot be transferred + */ + function _transfer(address, address, uint256) internal pure override { + revert('disabled for voting power'); + } +} diff --git a/zksync-ts/contracts/claim/abstract/ContinuousVesting.sol b/zksync-ts/contracts/claim/abstract/ContinuousVesting.sol new file mode 100644 index 00000000..9f9a252d --- /dev/null +++ b/zksync-ts/contracts/claim/abstract/ContinuousVesting.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { Distributor, AdvancedDistributor } from "./AdvancedDistributor.sol"; +import { IContinuousVesting } from "../../interfaces/IContinuousVesting.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +abstract contract ContinuousVesting is AdvancedDistributor, IContinuousVesting { + uint256 private start; // time vesting clock begins + uint256 private cliff; // time vesting begins (all tokens vested prior to the cliff are immediately claimable) + uint256 private end; // time vesting clock ends + + constructor( + IERC20 _token, + uint256 _total, + string memory _uri, + uint256 _voteFactor, + uint256 _start, + uint256 _cliff, + uint256 _end, + uint160 _maxDelayTime, + uint160 _salt + ) + // use a large fraction denominator to provide the highest resolution on continuous vesting. + AdvancedDistributor(_token, _total, _uri, _voteFactor, 10**18, _maxDelayTime, _salt) + { + require(_start <= _cliff, "vesting cliff before start"); + require(_cliff <= _end, "vesting end before cliff"); + require(_end <= 4102444800, "vesting ends after 4102444800 (Jan 1 2100)"); + + start = _start; + cliff = _cliff; + end = _end; + + emit SetContinuousVesting(start, cliff, end); + } + + function getVestedFraction( + address beneficiary, + uint256 time // time is in seconds past the epoch (e.g. block.timestamp) + ) public view override returns (uint256) { + uint256 delayedTime = time- getFairDelayTime(beneficiary); + // no tokens are vested + if (delayedTime <= cliff) { + return 0; + } + + // all tokens are vested + if (delayedTime >= end) { + return fractionDenominator; + } + + // some tokens are vested + return (fractionDenominator * (delayedTime - start)) / (end - start); + } + + function getVestingConfig() + external + view + returns ( + uint256, + uint256, + uint256 + ) + { + return (start, cliff, end); + } + + // Adjustable admin functions + function setVestingConfig( + uint256 _start, + uint256 _cliff, + uint256 _end + ) external onlyOwner { + start = _start; + cliff = _cliff; + end = _end; + emit SetContinuousVesting(start, cliff, end); + } +} diff --git a/zksync-ts/contracts/claim/abstract/CrosschainDistributor.sol b/zksync-ts/contracts/claim/abstract/CrosschainDistributor.sol new file mode 100644 index 00000000..7accfded --- /dev/null +++ b/zksync-ts/contracts/claim/abstract/CrosschainDistributor.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; + +import { AdvancedDistributor, IERC20 } from './AdvancedDistributor.sol'; +import { Distributor } from './Distributor.sol'; +import { IConnext } from '../../interfaces/IConnext.sol'; +import { ICrosschain } from '../../interfaces/ICrosschain.sol'; + +abstract contract CrosschainDistributor is AdvancedDistributor, ICrosschain { + using SafeERC20 for IERC20; + + IConnext public immutable connext; + uint32 public immutable domain; + + /** + * @notice Throws if the msg.sender is not connext + */ + modifier onlyConnext() { + require(msg.sender == address(connext), '!connext'); + _; + } + + constructor(IConnext _connext, uint256 _total) { + connext = _connext; + domain = uint32(_connext.domain()); + _allowConnext(_total); + } + + /** + @dev allows Connext to withdraw tokens for cross-chain settlement. Connext may withdraw up to + the remaining quantity of tokens that can be claimed - the allowance must be set for cross-chain claims. + */ + function _allowConnext(uint256 amount) internal { + token.safeApprove(address(connext), 0); + token.safeApprove(address(connext), amount); + } + + /** Reset Connext allowance when total is updated */ + function _setTotal(uint256 _total) internal virtual override onlyOwner { + // effects + super._setTotal(_total); + // interactions + _allowConnext(total - claimed); + } + + /** Reset Connext allowance when token is updated */ + function _setToken(IERC20 _token) internal virtual override nonReentrant onlyOwner { + // interaction before effect! + // decrease allowance on old token + _allowConnext(0); + + // effect + super._setToken(_token); + + // interactions + // increase allowance on new token + _allowConnext(total - claimed); + } + + /** + * @notice Settles claimed tokens to any valid Connext domain. + * @dev permissions are not checked: call only after a valid claim is executed + * @dev assumes connext fees are paid in native assets, not from the claim total + * @param _recipient: the address that will receive tokens + * @param _recipientDomain: the domain of the address that will receive tokens + * @param _amount: the amount of claims to settle + */ + function _settleClaim( + address _beneficiary, + address _recipient, + uint32 _recipientDomain, + uint256 _amount + ) internal virtual { + bytes32 id; + if (_recipientDomain == 0 || _recipientDomain == domain) { + token.safeTransfer(_recipient, _amount); + } else { + id = connext.xcall{value: msg.value}( + _recipientDomain, // destination domain + _recipient, // to + address(token), // asset + _recipient, // delegate, only required for self-execution + slippage + _amount, // amount + 0, // slippage -- assumes no pools on connext + bytes('') // calldata + ); + } + emit CrosschainClaim(id, _beneficiary, _recipient, _recipientDomain, _amount); + } +} diff --git a/zksync-ts/contracts/claim/abstract/CrosschainMerkleDistributor.sol b/zksync-ts/contracts/claim/abstract/CrosschainMerkleDistributor.sol new file mode 100644 index 00000000..b64e6dc6 --- /dev/null +++ b/zksync-ts/contracts/claim/abstract/CrosschainMerkleDistributor.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import { ICrosschain } from '../../interfaces/ICrosschain.sol'; +import { CrosschainDistributor } from './CrosschainDistributor.sol'; +import { AdvancedDistributor } from './AdvancedDistributor.sol'; +import { Distributor } from './Distributor.sol'; +import { MerkleSet } from './MerkleSet.sol'; +import { IConnext } from '../../interfaces/IConnext.sol'; +import { IDistributor } from '../../interfaces/IDistributor.sol'; +import { ECDSA } from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol'; + +/** + * @title CrosschainMerkleDistributor + * @author + * @notice Distributes funds to beneficiaries listed in a merkle proof on Connext-compatible chains. If a beneficiary is listed in multiple leaves, + * they can claim at most the max(amounts) rather than sum(amounts) -- each beneficiary gets a single distribution record across all chains or merkle leaves. + * + * @dev There are three ways to claim funds from this contract: + * + * 1. `claimBySignature` allows any address to claim funds on behalf of an EOA beneficiary to any Connext domain and recipient address (including recipients and domains not in the merkle leaf) by providing a merkle proof and beneficiary signature + * 2. `claimByMerkleProof` allows any address to claim funds on behalf of a beneficiary to the Connext domain and address specified in the merkle leaf by providing a merkle proof + * 3. `xReceive` allows any address on another Connext domain to claim funds on behalf of a beneficiary to the connext domain and address specified in the merkle leaf by providing a merkle proof + * + * A note on the merkle tree structure: + * + * The leaf structure used is: `hash(beneficiary, total, beneficiaryDomain)`. + * + * The contract is designed to support claims by both EOAs and contracts. If the beneficiary + * is a contract, the merkle leaf domain must match the contract domain. In this case, you can only guarantee the beneficiary + * controls their address on the domain the claim was initiated from (contracts do not share + * addresses across chains). Including the domain context in the leaf allows the contract to + * enforce this assertion via merkle proofs instead of using an authorized call (see: + * https://docs.connext.network/developers/guides/authentication). + */ +abstract contract CrosschainMerkleDistributor is CrosschainDistributor, MerkleSet { + event Foo(address bar); + constructor( + IConnext _connext, + bytes32 _merkleRoot, + uint256 _total + ) CrosschainDistributor(_connext, _total) MerkleSet(_merkleRoot) {} + + /// @dev public method to initialize a distribution record: requires a valid merkle proof + function initializeDistributionRecord( + uint32 _domain, // the domain of the beneficiary + address _beneficiary, // the address that will receive tokens + uint256 _amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) external validMerkleProof(_getLeaf(_beneficiary, _amount, _domain), merkleProof) { + _initializeDistributionRecord(_beneficiary, _amount); + } + + /** + * @notice Used for cross-chain claims via Satellite, which triggers claims through Connext. + * @dev This method is only callable by Connext, but anyone on any other Connext domain can + * trigger this method call on behalf of a beneficiary. Claimed funds will always be sent to + * the beneficiary address and beneficiary domain set in the merkle proof. + * @param _callData Calldata from origin initiator (Satellite). Should include proof, leaf information, and recipient + * information + */ + function xReceive( + bytes32, // _transferId, + uint256, // _amount, + address, // _asset, + address, // _originSender, + uint32, // _origin, + bytes calldata _callData + ) external onlyConnext returns (bytes memory) { + // Decode the data + (address beneficiary, uint32 beneficiaryDomain, uint256 totalAmount, bytes32[] memory proof) = abi + .decode(_callData, (address, uint32, uint256, bytes32[])); + _verifyMembership(_getLeaf(beneficiary, totalAmount, beneficiaryDomain), proof); + + // effects + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + + // interactions + // NOTE: xReceive is *NOT* payable (Connext does not handle native assets). + // Fees must be bumped after-the-fact for second-leg claims, or debited from the claim + // amount in a more advanced mechanism. + _settleClaim(beneficiary, beneficiary, beneficiaryDomain, claimedAmount); + + return bytes(''); + } + + /** + * @notice Claim tokens for a beneficiary using a merkle proof + * @dev This method can be called by anyone, but claimed funds will always be sent to the + * beneficiary address and domain set in the merkle proof. + * @param _beneficiary The address of the beneficiary + * @param _total The total claimable amount for this beneficiary + * @param _proof The merkle proof + */ + function claimByMerkleProof( + address _beneficiary, + uint256 _total, + bytes32[] calldata _proof + ) external payable { + _verifyMembership(_getLeaf(_beneficiary, _total, domain), _proof); + // effects + uint256 claimedAmount = _executeClaim(_beneficiary, _total); + + // interactions + _settleClaim(_beneficiary, _beneficiary, domain, claimedAmount); + } + + /** + * @notice Claim tokens for a beneficiary using a merkle proof and beneficiary signature. The beneficiary + * may specify any Connext domain and recipient address to receive the tokens. Will validate + * the proof and beneficiary signature, track the claim, and forward the funds to the designated + * recipient on the designated chain. + * @param _recipient The address to receive the claimed tokens + * @param _recipientDomain The domain of the recipient + * @param _beneficiary The address eligible to claim tokens based on a merkle leaf + * @param _beneficiaryDomain The domain of the beneficiary set in a merkle leaf + * @param _total The total quantity of tokens the beneficiary is eligible to claim + * @param _signature The signature of the beneficiary on the leaf + * @param _proof The merkle proof of the beneficiary leaf + */ + function claimBySignature( + address _recipient, + uint32 _recipientDomain, + address _beneficiary, + uint32 _beneficiaryDomain, + uint256 _total, + bytes calldata _signature, + bytes32[] calldata _proof + ) external payable { + // Recover the signature by beneficiary + bytes32 _signed = keccak256( + abi.encodePacked(_recipient, _recipientDomain, _beneficiary, _beneficiaryDomain, _total) + ); + address recovered = _recoverSignature(_signed, _signature); + require(recovered == _beneficiary, '!recovered'); + + // Validate the claim + _verifyMembership(_getLeaf(_beneficiary, _total, _beneficiaryDomain), _proof); + uint256 claimedAmount = _executeClaim(_beneficiary, _total); + + _settleClaim(_beneficiary, _recipient, _recipientDomain, claimedAmount); + } + + /** + * @notice Allows the owner update the merkle root + * @param _merkleRoot The new merkle root + */ + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } + + /** + * @notice Recover the signing address from an encoded payload. + * @dev Will hash and convert to an eth signed message. + * @param _signed The hash that was signed. + * @param _sig The signature from which we will recover the signer. + */ + function _recoverSignature(bytes32 _signed, bytes calldata _sig) internal pure returns (address) { + // Recover + return ECDSA.recover(ECDSA.toEthSignedMessageHash(_signed), _sig); + } + + /** + * @notice Generates the leaf from plaintext + * @param _domain Beneficiary domain + * @param _beneficiary Beneficiary address on domain + * @param _total Total claim amount for the beneficiary + */ + function _getLeaf( + address _beneficiary, // the address that will receive tokens + uint256 _total, + uint32 _domain // the domain of the recipient + ) internal pure returns (bytes32 _leaf) { + _leaf = keccak256(abi.encodePacked(_beneficiary, _total, _domain)); + } +} \ No newline at end of file diff --git a/zksync-ts/contracts/claim/abstract/Distributor.sol b/zksync-ts/contracts/claim/abstract/Distributor.sol new file mode 100644 index 00000000..22a97755 --- /dev/null +++ b/zksync-ts/contracts/claim/abstract/Distributor.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import { ReentrancyGuard } from '@openzeppelin/contracts/security/ReentrancyGuard.sol'; + +import { IDistributor, DistributionRecord } from '../../interfaces/IDistributor.sol'; + +/** + * @title Distributor + * @notice Distributes funds to beneficiaries and tracks distribution status + */ +abstract contract Distributor is IDistributor, ReentrancyGuard { + using SafeERC20 for IERC20; + + mapping(address => DistributionRecord) internal records; // track distribution records per user + IERC20 public token; // the token being claimed + uint256 public total; // total tokens allocated for claims + uint256 public claimed; // tokens already claimed + string public uri; // ipfs link on distributor info + uint256 immutable fractionDenominator; // denominator for vesting fraction (e.g. if vested fraction is 100 and fractionDenominator is 10000, 1% of tokens have vested) + + // provide context on the contract name and version + function NAME() external view virtual returns (string memory); + + function VERSION() external view virtual returns (uint256); + + constructor(IERC20 _token, uint256 _total, string memory _uri, uint256 _fractionDenominator) { + require(address(_token) != address(0), 'Distributor: token is address(0)'); + require(_total > 0, 'Distributor: total is 0'); + + token = _token; + total = _total; + uri = _uri; + fractionDenominator = _fractionDenominator; + emit InitializeDistributor(token, total, uri, fractionDenominator); + } + + /** + * @dev Set up the distribution record for a user. Permissions are not checked in this function. + * Amount is limited to type(uint120).max to allow each DistributionRecord to be packed into a single storage slot. + * + * @param beneficiary The address of the beneficiary + * @param _totalAmount The total amount of tokens to be distributed to the beneficiary + */ + function _initializeDistributionRecord( + address beneficiary, + uint256 _totalAmount + ) internal virtual { + + // Checks + require(_totalAmount <= type(uint120).max, 'Distributor: totalAmount > type(uint120).max'); + uint120 totalAmount = uint120(_totalAmount); + + // Effects - note that the existing claimed quantity is re-used during re-initialization + records[beneficiary] = DistributionRecord(true, totalAmount, records[beneficiary].claimed); + emit InitializeDistributionRecord(beneficiary, totalAmount); + } + + /** + * @notice Record the claim internally: + * @dev This function does not check permissions: caller must verify the claim is valid! + * this function should not call any untrusted external contracts to avoid reentrancy + */ + function _executeClaim( + address beneficiary, + uint256 _totalAmount + ) internal virtual returns (uint256) { + uint120 totalAmount = uint120(_totalAmount); + + // effects + if (records[beneficiary].total != totalAmount) { + // re-initialize if the total has been updated + _initializeDistributionRecord(beneficiary, totalAmount); + } + + uint120 claimableAmount = uint120(getClaimableAmount(beneficiary)); + require(claimableAmount > 0, 'Distributor: no more tokens claimable right now'); + + records[beneficiary].claimed += claimableAmount; + claimed += claimableAmount; + + return claimableAmount; + } + + /** + * @dev Move tokens associated with the claim to the recipient. This function should be called + * after the claim has been executed internally to avoid reentrancy issues. + * @param _recipient The address of the recipient + * @param _amount The amount of tokens to be transferred during this claim + */ + function _settleClaim(address _recipient, uint256 _amount) internal virtual { + token.safeTransfer(_recipient, _amount); + emit Claim(_recipient, _amount); + } + + /// @notice return a distribution record + function getDistributionRecord( + address beneficiary + ) external view virtual returns (DistributionRecord memory) { + return records[beneficiary]; + } + + // Get tokens vested as fraction of fractionDenominator + function getVestedFraction( + address beneficiary, + uint256 time + ) public view virtual returns (uint256); + + function getFractionDenominator() public view returns (uint256) { + return fractionDenominator; + } + + // get the number of tokens currently claimable by a specific use + function getClaimableAmount(address beneficiary) public view virtual returns (uint256) { + require(records[beneficiary].initialized, 'Distributor: claim not initialized'); + + DistributionRecord memory record = records[beneficiary]; + + uint256 claimable = (record.total * getVestedFraction(beneficiary, block.timestamp)) / + fractionDenominator; + return + record.claimed >= claimable + ? 0 // no more tokens to claim + : claimable - record.claimed; // claim all available tokens + } +} diff --git a/zksync-ts/contracts/claim/abstract/MerkleSet.sol b/zksync-ts/contracts/claim/abstract/MerkleSet.sol new file mode 100644 index 00000000..200fd88b --- /dev/null +++ b/zksync-ts/contracts/claim/abstract/MerkleSet.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import '@openzeppelin/contracts/utils/cryptography/MerkleProof.sol'; +import { IMerkleSet } from '../../interfaces/IMerkleSet.sol'; + +/** + * @title MerkleSet + * @notice Checks merkle proofs + * @dev Contracts inheriting from MerkleSet may update the merkle root whenever desired. + */ +contract MerkleSet is IMerkleSet { + bytes32 private merkleRoot; + + constructor(bytes32 _merkleRoot) { + _setMerkleRoot(_merkleRoot); + } + + modifier validMerkleProof(bytes32 leaf, bytes32[] memory merkleProof) { + _verifyMembership(leaf, merkleProof); + + _; + } + + /** + * @notice Tests membership in the merkle set + */ + function _testMembership( + bytes32 leaf, + bytes32[] memory merkleProof + ) internal view returns (bool) { + return MerkleProof.verify(merkleProof, merkleRoot, leaf); + } + + function getMerkleRoot() public view returns (bytes32) { + return merkleRoot; + } + + /** + * @dev Verifies membership in the merkle set + */ + function _verifyMembership(bytes32 leaf, bytes32[] memory merkleProof) internal view { + require(_testMembership(leaf, merkleProof), 'invalid proof'); + } + + /** + * @dev Updates the merkle root + */ + function _setMerkleRoot(bytes32 _merkleRoot) internal { + merkleRoot = _merkleRoot; + emit SetMerkleRoot(merkleRoot); + } +} diff --git a/zksync-ts/contracts/claim/abstract/PriceTierVesting.sol b/zksync-ts/contracts/claim/abstract/PriceTierVesting.sol new file mode 100644 index 00000000..8149ae6b --- /dev/null +++ b/zksync-ts/contracts/claim/abstract/PriceTierVesting.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { AdvancedDistributor, Distributor, IERC20 } from "./AdvancedDistributor.sol"; +import { IPriceTierVesting, PriceTier } from "../../interfaces/IPriceTierVesting.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; + +abstract contract PriceTierVesting is AdvancedDistributor, IPriceTierVesting { + PriceTier[] private tiers; + uint256 private start; // time vesting begins + uint256 private end; // time vesting ends (all tokens are claimable) + IOracleOrL2OracleWithSequencerCheck private oracle; // oracle providing prices + + constructor( + IERC20 _token, + uint256 _total, + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _voteFactor, + uint256 _start, + uint256 _end, + IOracleOrL2OracleWithSequencerCheck _oracle, + PriceTier[] memory _tiers, + uint160 _maxDelayTime, + uint160 _salt + ) AdvancedDistributor(_token, _total, _uri, _voteFactor, 10000, _maxDelayTime, _salt) { + _setPriceTiers(_start, _end, _oracle, _tiers); + } + + function _getOraclePrice() private view returns (uint256) { + ( + uint80 roundID, + int256 _price, + /* uint256 startedAt */, + uint256 timeStamp, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound != 0, "answer == 0"); + require(timeStamp != 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + + return uint256(_price); + } + + function getStart() external view override returns (uint256) { + return start; + } + + function getEnd() external view override returns (uint256) { + return end; + } + + function getOracle() external view override returns (IOracleOrL2OracleWithSequencerCheck) { + return oracle; + } + + function getPriceTier(uint256 i) public view returns (PriceTier memory) { + return tiers[i]; + } + + function getPriceTiers() public view returns (PriceTier[] memory) { + return tiers; + } + + function getVestedFraction( + address beneficiary, + uint256 time // time in seconds past epoch + ) public view override returns (uint256) { + // shift this user's time by their fair delay + uint256 delayedTime = time - getFairDelayTime(beneficiary); + + // no tokens are vested + if (delayedTime < start) { + return 0; + } + + // all tokens are vested + if (delayedTime >= end) { + return fractionDenominator; + } + + uint256 price = _getOraclePrice(); + + for (uint256 i = tiers.length; i != 0; ) { + unchecked { + --i; + } + if (price > tiers[i].price) { + return tiers[i].vestedFraction; + } + } + return 0; + } + + function _setPriceTiers( + uint256 _start, + uint256 _end, + IOracleOrL2OracleWithSequencerCheck _oracle, + PriceTier[] memory _tiers + ) private { + require(_tiers.length > 0, "1+ price tiers required"); + + delete tiers; + + uint128 highestPrice = 0; + uint128 highestFraction = 0; + + for (uint256 i = 0; i < _tiers.length; ) { + require(_tiers[i].price > highestPrice, "tier prices decrease"); + require(_tiers[i].vestedFraction > highestFraction, "vested fraction decreases"); + highestPrice = _tiers[i].price; + highestFraction = _tiers[i].vestedFraction; + + tiers.push(_tiers[i]); + + unchecked { + ++i; + } + } + + require(highestFraction == fractionDenominator, "highest price tier must vest all tokens"); + require(address(_oracle) != address(0), "oracle == address(0)"); + + start = _start; + end = _end; + oracle = _oracle; + emit SetPriceTierConfig(start, end, oracle, tiers); + } + + // Adjustable admin functions + function setPriceTiers( + uint256 _start, + uint256 _end, + IOracleOrL2OracleWithSequencerCheck _oracle, + PriceTier[] memory _tiers + ) external onlyOwner { + _setPriceTiers(_start, _end, _oracle, _tiers); + } +} diff --git a/zksync-ts/contracts/claim/abstract/TrancheVesting.sol b/zksync-ts/contracts/claim/abstract/TrancheVesting.sol new file mode 100644 index 00000000..9fc5d31c --- /dev/null +++ b/zksync-ts/contracts/claim/abstract/TrancheVesting.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { AdvancedDistributor } from "./AdvancedDistributor.sol"; +import { ITrancheVesting, Tranche } from "../../interfaces/ITrancheVesting.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "hardhat/console.sol"; + +/** + * @title TrancheVesting + * @notice Distributes funds to beneficiaries over time in tranches. + */ +abstract contract TrancheVesting is AdvancedDistributor, ITrancheVesting { + // time and vested fraction must monotonically increase in the tranche array + Tranche[] private tranches; + + constructor( + IERC20 _token, + uint256 _total, + string memory _uri, + uint256 _voteFactor, + Tranche[] memory _tranches, + uint160 _maxDelayTime, + uint160 _salt + ) + AdvancedDistributor( + _token, + _total, + _uri, + _voteFactor, + 10000, + _maxDelayTime, + _salt + ) + { + // tranches can be set in the constructor or in setTranches() + _setTranches(_tranches); + } + + /** + * @notice Get the vested fraction for a beneficiary at a given time. + * @dev Before the first tranche time, the vested fraction will be 0. At times between + * tranche_i and tranche_i+1, the vested fraction will be tranche_i+1's vested fraction. + * After the last tranche time, the vested fraction will be the fraction denominator. + */ + function getVestedFraction( + address beneficiary, + uint256 time + ) public view override returns (uint256) { + uint256 delay = getFairDelayTime(beneficiary); + for (uint256 i = tranches.length; i != 0; ) { + unchecked { + --i; + } + + if (time - delay > tranches[i].time) { + return tranches[i].vestedFraction; + } + } + + return 0; + } + + // Get a single tranche + function getTranche(uint256 i) public view returns (Tranche memory) { + return tranches[i]; + } + + // Get all tranches + function getTranches() public view returns (Tranche[] memory) { + return tranches; + } + + /** + * @dev Tranches can be updated at any time. The quantity of tokens a user can claim is based on the + * claimable quantity at the time of the claim and the quantity of tokens already claimed by the user. + */ + function _setTranches(Tranche[] memory _tranches) private { + require(_tranches.length != 0, "tranches required"); + + delete tranches; + + uint128 lastTime = 0; + uint128 lastVestedFraction = 0; + + for (uint256 i = 0; i < _tranches.length; ) { + require( + _tranches[i].vestedFraction != 0, + "tranche vested fraction == 0" + ); + require(_tranches[i].time > lastTime, "tranche time must increase"); + require( + _tranches[i].vestedFraction > lastVestedFraction, + "tranche vested fraction must increase" + ); + lastTime = _tranches[i].time; + lastVestedFraction = _tranches[i].vestedFraction; + tranches.push(_tranches[i]); + + emit SetTranche(i, lastTime, lastVestedFraction); + + unchecked { + ++i; + } + } + + require( + lastTime <= 4102444800, + "vesting ends after 4102444800 (Jan 1 2100)" + ); + require( + lastVestedFraction == fractionDenominator, + "last tranche must vest all tokens" + ); + } + + /** + * @notice Set the vesting tranches. Tranches must be sorted by time and vested fraction must monotonically increase. + * The last tranche must vest all tokens (vestedFraction == fractionDenominator) + */ + function setTranches(Tranche[] memory _tranches) external onlyOwner { + _setTranches(_tranches); + } +} diff --git a/zksync-ts/contracts/claim/distributor-v4/ContinuousVestingMerkleDistributor.sol b/zksync-ts/contracts/claim/distributor-v4/ContinuousVestingMerkleDistributor.sol new file mode 100644 index 00000000..8dca38d4 --- /dev/null +++ b/zksync-ts/contracts/claim/distributor-v4/ContinuousVestingMerkleDistributor.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import "@openzeppelin/contracts/utils/Strings.sol"; + +import {IFeeLevelJudge} from "../IFeeLevelJudge.sol"; +import "../factory/ContinuousVestingInitializable.sol"; +import "../factory/MerkleSetInitializable.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import "../../config/INetworkConfig.sol"; + +contract ContinuousVestingMerkleDistributor_v_4_0 is Initializable, ContinuousVestingInitializable, MerkleSetInitializable { + using Address for address payable; + using SafeERC20 for IERC20; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + INetworkConfig networkConfig; + // denominator used to determine size of fee bips + uint256 constant feeFractionDenominator = 10000; + + constructor() { + _disableInitializers(); + } + + function initialize( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig + ) public initializer { + __ContinuousVesting_init( + _token, _total, _uri, _start, _cliff, _end, _maxDelayTime, uint160(uint256(_merkleRoot)), _owner + ); + + __MerkleSet_init(_merkleRoot); + + _transferOwnership(_owner); + + networkConfig = _networkConfig; + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge(networkConfig.getStakingAddress()); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + + // TODO: reduce duplication with other contracts + uint256 feeAmount = (_total * feeLevel) / feeFractionDenominator; + if (_autoPull) { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), _total + feeAmount); + + _token.approve(address(this), 0); + _token.approve(address(this), feeAmount); + _token.safeTransferFrom(address(this), networkConfig.getFeeRecipient(), feeAmount); + } else { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), feeAmount); + } + } + + function NAME() external pure override returns (string memory) { + return "ContinuousVestingMerkleInitializable"; + } + + function VERSION() external pure override returns (uint256) { + return 4; + } + + function initializeDistributionRecord( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) external validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, amount)), merkleProof) { + _initializeDistributionRecord(beneficiary, amount); + } + + function claim( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) + external + payable + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount)), merkleProof) + nonReentrant + { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(baseCurrencyValue >= platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(_msgSender()).sendValue(msg.value - feeAmountInWei); + + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } + + // TODO: reduce duplication between other contracts + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + // TODO: reduce duplication between other contracts + // Get a positive token price from a chainlink oracle + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/zksync-ts/contracts/claim/distributor-v4/ContinuousVestingMerkleDistributorFactory.sol b/zksync-ts/contracts/claim/distributor-v4/ContinuousVestingMerkleDistributorFactory.sol new file mode 100644 index 00000000..e5cebdd8 --- /dev/null +++ b/zksync-ts/contracts/claim/distributor-v4/ContinuousVestingMerkleDistributorFactory.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import {INetworkConfig} from "../../config/INetworkConfig.sol"; +import {ContinuousVestingMerkleDistributor_v_4_0} from "./ContinuousVestingMerkleDistributor.sol"; + +contract ContinuousVestingMerkleDistributorFactory_v_4_0 { + using Address for address payable; + + address private immutable i_implementation; + address[] public distributors; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + event DistributorDeployed(address indexed distributor); + + constructor(address implementation) { + i_implementation = implementation; + } + + function _getSalt( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) private pure returns (bytes32) { + return keccak256(abi.encode( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + )); + } + + function deployDistributor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public payable returns (ContinuousVestingMerkleDistributor_v_4_0 distributor) { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(_networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = _networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 nativeBaseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(nativeBaseCurrencyValue >= _platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((_platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + _platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(msg.sender).sendValue(msg.value - feeAmountInWei); + + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + distributor = + ContinuousVestingMerkleDistributor_v_4_0(Clones.cloneDeterministic(i_implementation, salt)); + distributors.push(address(distributor)); + + emit DistributorDeployed(address(distributor)); + + distributor.initialize(_token, _total, _uri, _start, _cliff, _end, _merkleRoot, _maxDelayTime, _owner, _feeOrSupplyHolder, _autoPull, _networkConfig); + + return distributor; + } + + function getImplementation() public view returns (address) { + return i_implementation; + } + + function predictDistributorAddress( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public view returns (address) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + return Clones.predictDeterministicAddress(i_implementation, salt, address(this)); + } + + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/zksync-ts/contracts/claim/distributor-v4/TrancheVestingMerkleDistributor.sol b/zksync-ts/contracts/claim/distributor-v4/TrancheVestingMerkleDistributor.sol new file mode 100644 index 00000000..67e3d4d1 --- /dev/null +++ b/zksync-ts/contracts/claim/distributor-v4/TrancheVestingMerkleDistributor.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import "@openzeppelin/contracts/utils/Strings.sol"; + +import {IFeeLevelJudge} from "../IFeeLevelJudge.sol"; +import "../factory/ContinuousVestingInitializable.sol"; +import "../factory/TrancheVestingInitializable.sol"; +import "../factory/MerkleSetInitializable.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import "../../config/INetworkConfig.sol"; + +contract TrancheVestingMerkleDistributor_v_4_0 is + Initializable, + TrancheVestingInitializable, + MerkleSetInitializable +{ + using Address for address payable; + using SafeERC20 for IERC20; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; // number of decimals for ETH + INetworkConfig networkConfig; + // denominator used to determine size of fee bips + uint256 constant feeFractionDenominator = 10000; + constructor() { + _disableInitializers(); + } + + function initialize( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig + ) public initializer { + __TrancheVesting_init(_token, _total, _uri, _tranches, _maxDelayTime, uint160(uint256(_merkleRoot)), _owner); + + __MerkleSet_init(_merkleRoot); + + _transferOwnership(_owner); + + networkConfig = _networkConfig; + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge(networkConfig.getStakingAddress()); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + + // TODO: reduce duplication with other contracts + uint256 feeAmount = (_total * feeLevel) / feeFractionDenominator; + if (_autoPull) { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), _total + feeAmount); + + _token.approve(address(this), 0); + _token.approve(address(this), feeAmount); + _token.safeTransferFrom(address(this), networkConfig.getFeeRecipient(), feeAmount); + } else { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), feeAmount); + } + } + + function NAME() external pure override returns (string memory) { + return "TrancheVestingMerkleDistributor"; + } + + function VERSION() external pure override returns (uint256) { + return 4; + } + + function initializeDistributionRecord( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) external validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, amount)), merkleProof) { + _initializeDistributionRecord(beneficiary, amount); + } + + function claim( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) + external + payable + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount)), merkleProof) + nonReentrant + { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(baseCurrencyValue >= platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(_msgSender()).sendValue(msg.value - feeAmountInWei); + + // effects + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } + + // TODO: reduce duplication between other contracts + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + // TODO: reduce duplication between other contracts + // Get a positive token price from a chainlink oracle + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/zksync-ts/contracts/claim/distributor-v4/TrancheVestingMerkleDistributorFactory.sol b/zksync-ts/contracts/claim/distributor-v4/TrancheVestingMerkleDistributorFactory.sol new file mode 100644 index 00000000..473aae0f --- /dev/null +++ b/zksync-ts/contracts/claim/distributor-v4/TrancheVestingMerkleDistributorFactory.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import {TrancheVestingMerkleDistributor_v_4_0, Tranche} from "./TrancheVestingMerkleDistributor.sol"; +import {INetworkConfig} from "../../config/INetworkConfig.sol"; + +contract TrancheVestingMerkleDistributorFactory_v_4_0 { + using Address for address payable; + + address private immutable i_implementation; + address[] public distributors; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + event DistributorDeployed(address indexed distributor); + + constructor(address implementation) { + i_implementation = implementation; + } + + function _getSalt( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) private pure returns (bytes32) { + return keccak256(abi.encode( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + )); + } + + function deployDistributor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public payable returns (TrancheVestingMerkleDistributor_v_4_0 distributor) { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(_networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = _networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 nativeBaseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(nativeBaseCurrencyValue >= _platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((_platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + _platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(msg.sender).sendValue(msg.value - feeAmountInWei); + + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + distributor = + TrancheVestingMerkleDistributor_v_4_0(Clones.cloneDeterministic(i_implementation, salt)); + distributors.push(address(distributor)); + + emit DistributorDeployed(address(distributor)); + + distributor.initialize(_token, _total, _uri, _tranches, _merkleRoot, _maxDelayTime, _owner, _feeOrSupplyHolder, _autoPull, _networkConfig); + + return distributor; + } + + function getImplementation() public view returns (address) { + return i_implementation; + } + + function predictDistributorAddress( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public view returns (address) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + return Clones.predictDeterministicAddress(i_implementation, salt, address(this)); + } + + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/zksync-ts/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributor.sol b/zksync-ts/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributor.sol new file mode 100644 index 00000000..62d8c099 --- /dev/null +++ b/zksync-ts/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributor.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import "@openzeppelin/contracts/utils/Strings.sol"; + +import {IFeeLevelJudge} from "../IFeeLevelJudge.sol"; +import "../factory/ContinuousVestingInitializable.sol"; +import "../../utilities/AccessVerifier.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import "../../config/INetworkConfig.sol"; + +contract ContinuousVestingMerkleDistributor_v_5_0 is Initializable, ContinuousVestingInitializable, AccessVerifier { + using Address for address payable; + using SafeERC20 for IERC20; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + INetworkConfig networkConfig; + // denominator used to determine size of fee bips + uint256 constant feeFractionDenominator = 10000; + + constructor() { + _disableInitializers(); + } + + function initialize( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // (deprecated) the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig + ) public initializer { + __ContinuousVesting_init( + _token, _total, _uri, _start, _cliff, _end, _maxDelayTime, uint160(uint256(_merkleRoot)), _owner + ); + + _transferOwnership(_owner); + + networkConfig = _networkConfig; + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge(networkConfig.getStakingAddress()); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + + // TODO: reduce duplication with other contracts + uint256 feeAmount = (_total * feeLevel) / feeFractionDenominator; + if (_autoPull) { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), _total + feeAmount); + + _token.approve(address(this), 0); + _token.approve(address(this), feeAmount); + _token.safeTransferFrom(address(this), networkConfig.getFeeRecipient(), feeAmount); + } else { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), feeAmount); + } + } + + function NAME() external pure override returns (string memory) { + return "ContinuousVestingMerkleInitializable"; + } + + function VERSION() external pure override returns (uint256) { + return 5; + } + + modifier validSignature(uint256 totalAmount, uint64 expiresAt, bytes memory signature) { + verifyAccessSignature(networkConfig.getAccessAuthorityAddress(), _msgSender(), totalAmount, expiresAt, signature); + + _; + } + + function claim( + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) + external + payable + validSignature(totalAmount, expiresAt, signature) + nonReentrant + { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(baseCurrencyValue >= platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(_msgSender()).sendValue(msg.value - feeAmountInWei); + + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + // TODO: reduce duplication between other contracts + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + // TODO: reduce duplication between other contracts + // Get a positive token price from a chainlink oracle + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/zksync-ts/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributorFactory.sol b/zksync-ts/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributorFactory.sol new file mode 100644 index 00000000..3ca0fb3b --- /dev/null +++ b/zksync-ts/contracts/claim/distributor-v5/ContinuousVestingMerkleDistributorFactory.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import {INetworkConfig} from "../../config/INetworkConfig.sol"; +import {ContinuousVestingMerkleDistributor_v_5_0} from "./ContinuousVestingMerkleDistributor.sol"; + +contract ContinuousVestingMerkleDistributorFactory_v_5_0 { + using Address for address payable; + + address private immutable i_implementation; + address[] public distributors; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + event DistributorDeployed(address indexed distributor); + + constructor(address implementation) { + i_implementation = implementation; + } + + function _getSalt( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) private pure returns (bytes32) { + return keccak256(abi.encode( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + )); + } + + function deployDistributor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public payable returns (ContinuousVestingMerkleDistributor_v_5_0 distributor) { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(_networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = _networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 nativeBaseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(nativeBaseCurrencyValue >= _platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((_platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + _platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(msg.sender).sendValue(msg.value - feeAmountInWei); + + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + distributor = + ContinuousVestingMerkleDistributor_v_5_0(Clones.cloneDeterministic(i_implementation, salt)); + distributors.push(address(distributor)); + + emit DistributorDeployed(address(distributor)); + + distributor.initialize(_token, _total, _uri, _start, _cliff, _end, _merkleRoot, _maxDelayTime, _owner, _feeOrSupplyHolder, _autoPull, _networkConfig); + + return distributor; + } + + function getImplementation() public view returns (address) { + return i_implementation; + } + + function predictDistributorAddress( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public view returns (address) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + return Clones.predictDeterministicAddress(i_implementation, salt, address(this)); + } + + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/zksync-ts/contracts/claim/distributor-v5/TrancheVestingMerkleDistributor.sol b/zksync-ts/contracts/claim/distributor-v5/TrancheVestingMerkleDistributor.sol new file mode 100644 index 00000000..92f6a3cd --- /dev/null +++ b/zksync-ts/contracts/claim/distributor-v5/TrancheVestingMerkleDistributor.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import "@openzeppelin/contracts/utils/Strings.sol"; + +import {IFeeLevelJudge} from "../IFeeLevelJudge.sol"; +import "../../utilities/AccessVerifier.sol"; +import "../factory/ContinuousVestingInitializable.sol"; +import "../factory/TrancheVestingInitializable.sol"; +import "../factory/MerkleSetInitializable.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import "../../config/INetworkConfig.sol"; + +contract TrancheVestingMerkleDistributor_v_5_0 is + Initializable, + TrancheVestingInitializable, + AccessVerifier +{ + using Address for address payable; + using SafeERC20 for IERC20; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; // number of decimals for ETH + INetworkConfig networkConfig; + // denominator used to determine size of fee bips + uint256 constant feeFractionDenominator = 10000; + constructor() { + _disableInitializers(); + } + + function initialize( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig + ) public initializer { + __TrancheVesting_init(_token, _total, _uri, _tranches, _maxDelayTime, uint160(uint256(_merkleRoot)), _owner); + + _transferOwnership(_owner); + + networkConfig = _networkConfig; + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge(networkConfig.getStakingAddress()); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + + // TODO: reduce duplication with other contracts + uint256 feeAmount = (_total * feeLevel) / feeFractionDenominator; + if (_autoPull) { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), _total + feeAmount); + + _token.approve(address(this), 0); + _token.approve(address(this), feeAmount); + _token.safeTransferFrom(address(this), networkConfig.getFeeRecipient(), feeAmount); + } else { + _token.safeTransferFrom(_feeOrSupplyHolder, address(this), feeAmount); + } + } + + function NAME() external pure override returns (string memory) { + return "TrancheVestingMerkleDistributor"; + } + + function VERSION() external pure override returns (uint256) { + return 5; + } + + modifier validSignature(uint256 totalAmount, uint64 expiresAt, bytes memory signature) { + verifyAccessSignature(networkConfig.getAccessAuthorityAddress(), _msgSender(), totalAmount, expiresAt, signature); + + _; + } + + function claim( + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) + external + payable + validSignature(totalAmount, expiresAt, signature) + nonReentrant + { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(baseCurrencyValue >= platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(_msgSender()).sendValue(msg.value - feeAmountInWei); + + // effects + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + // TODO: reduce duplication between other contracts + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + // TODO: reduce duplication between other contracts + // Get a positive token price from a chainlink oracle + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/zksync-ts/contracts/claim/distributor-v5/TrancheVestingMerkleDistributorFactory.sol b/zksync-ts/contracts/claim/distributor-v5/TrancheVestingMerkleDistributorFactory.sol new file mode 100644 index 00000000..56ed2bd1 --- /dev/null +++ b/zksync-ts/contracts/claim/distributor-v5/TrancheVestingMerkleDistributorFactory.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import {TrancheVestingMerkleDistributor_v_5_0, Tranche} from "./TrancheVestingMerkleDistributor.sol"; +import {INetworkConfig} from "../../config/INetworkConfig.sol"; + +contract TrancheVestingMerkleDistributorFactory_v_5_0 { + using Address for address payable; + + address private immutable i_implementation; + address[] public distributors; + + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + event DistributorDeployed(address indexed distributor); + + constructor(address implementation) { + i_implementation = implementation; + } + + function _getSalt( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) private pure returns (bytes32) { + return keccak256(abi.encode( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + )); + } + + function deployDistributor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public payable returns (TrancheVestingMerkleDistributor_v_5_0 distributor) { + IOracleOrL2OracleWithSequencerCheck nativeTokenPriceOracle = IOracleOrL2OracleWithSequencerCheck(_networkConfig.getNativeTokenPriceOracleAddress()); + uint256 nativeTokenPriceOracleHeartbeat = _networkConfig.getNativeTokenPriceOracleHeartbeat(); + + uint256 nativeBaseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(nativeBaseCurrencyValue >= _platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((_platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + _platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + payable(msg.sender).sendValue(msg.value - feeAmountInWei); + + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + distributor = + TrancheVestingMerkleDistributor_v_5_0(Clones.cloneDeterministic(i_implementation, salt)); + distributors.push(address(distributor)); + + emit DistributorDeployed(address(distributor)); + + distributor.initialize(_token, _total, _uri, _tranches, _merkleRoot, _maxDelayTime, _owner, _feeOrSupplyHolder, _autoPull, _networkConfig); + + return distributor; + } + + function getImplementation() public view returns (address) { + return i_implementation; + } + + function predictDistributorAddress( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + address _feeOrSupplyHolder, + bool _autoPull, + INetworkConfig _networkConfig, + address payable _platformFlatRateFeeRecipient, + uint256 _platformFlatRateFeeAmount, + uint256 _nonce + ) public view returns (address) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _feeOrSupplyHolder, + _autoPull, + _networkConfig, + _platformFlatRateFeeRecipient, + _platformFlatRateFeeAmount, + _nonce + ); + + return Clones.predictDeterministicAddress(i_implementation, salt, address(this)); + } + + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + require(updatedAt < block.timestamp - heartbeat, "stale price"); + + return uint256(_price); + } +} diff --git a/zksync-ts/contracts/claim/factory/AdvancedDistributorInitializable.sol b/zksync-ts/contracts/claim/factory/AdvancedDistributorInitializable.sol new file mode 100644 index 00000000..37bc5bf1 --- /dev/null +++ b/zksync-ts/contracts/claim/factory/AdvancedDistributorInitializable.sol @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC20Votes, ERC20Permit, ERC20} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import {DistributorInitializable, DistributionRecord, IERC20} from "./DistributorInitializable.sol"; +import {IAdjustable} from "../../interfaces/IAdjustable.sol"; +import {IVoting} from "../../interfaces/IVoting.sol"; +import {Sweepable} from "../../utilities/Sweepable.sol"; +import "./FairQueueInitializable.sol"; + +/** + * @title AdvancedDistributor + * @notice Distributes tokens to beneficiaries with voting-while-vesting and administrative controls. + * The contract owner can control these distribution parameters: + * - the merkle root determining all distribution details + * - adjustments to specific distributions + * - the token being distributed + * - the total amount to distribute + * - the metadata URI + * - the voting power of undistributed tokens + * - the recipient of swept funds + * + * This contract also allows owners to perform several other admin functions + * - updating the contract owner + * - sweeping tokens and native currency to a recipient + * + * This contract tracks beneficiary voting power through an internal ERC20Votes token that cannot be transferred. The + * beneficiary must delegate to an address to use this voting power. Their voting power decreases when the token is claimed. + * + * @dev Updates to the contract must follow these constraints: + * - If a merkle root update alters the total token quantity to distribute across all users, also adjust the total value. + * The DistributionRecord for each beneficiary updated in the merkle root will be incorrect until a claim is executed. + * - If the total changes, make sure to add or withdraw tokens being distributed to match the new total. + */ +abstract contract AdvancedDistributorInitializable is + Initializable, + Ownable, + Sweepable, + ERC20Votes, + DistributorInitializable, + IAdjustable, + IVoting, + FairQueueInitializable +{ + using SafeERC20 for IERC20; + + uint256 private voteFactor; + + constructor() + ERC20Permit("Internal vote tracker") + ERC20("Internal vote tracker", "IVT") + Sweepable(payable(msg.sender)) + {} + + function __AdvancedDistributor_init( + IERC20 _token, + uint256 _total, + string memory _uri, + uint256 _voteFactor, + uint256 _fractionDenominator, + uint160 _maxDelayTime, + uint160 _salt, + address _owner + ) internal onlyInitializing { + _setSweepRecipient(payable(_owner)); + voteFactor = _voteFactor; + emit SetVoteFactor(voteFactor); + + __Distributor_init(_token, _total, _uri, _fractionDenominator); + __FairQueue_init(_maxDelayTime, _salt); + } + + /** + * convert a token quantity to a vote quantity + */ + function tokensToVotes(uint256 tokenAmount) private view returns (uint256) { + return (tokenAmount * voteFactor) / fractionDenominator; + } + + // Update voting power based on distribution record initialization or claims + function _reconcileVotingPower(address beneficiary) private { + // current potential voting power + uint256 currentVotes = balanceOf(beneficiary); + // correct voting power after initialization, claim, or adjustment + DistributionRecord memory record = records[beneficiary]; + uint256 newVotes = record.claimed >= record.total ? 0 : tokensToVotes(record.total - record.claimed); + + if (currentVotes > newVotes) { + // reduce voting power through ERC20Votes extension + _burn(beneficiary, currentVotes - newVotes); + } else if (currentVotes < newVotes) { + // increase voting power through ERC20Votes extension + _mint(beneficiary, newVotes - currentVotes); + } + } + + function _initializeDistributionRecord(address beneficiary, uint256 totalAmount) internal virtual override { + super._initializeDistributionRecord(beneficiary, totalAmount); + _reconcileVotingPower(beneficiary); + } + + function _executeClaim(address beneficiary, uint256 totalAmount) + internal + virtual + override + returns (uint256 _claimed) + { + _claimed = super._executeClaim(beneficiary, totalAmount); + _reconcileVotingPower(beneficiary); + } + + /** + * @dev Adjust the quantity claimable by a user, overriding the value in the distribution record. + * + * Note: If used in combination with merkle proofs, adjustments to a beneficiary's total could be + * reset by anyone to the value in the merkle leaf at any time. Update the merkle root instead. + * + * Amount is limited to type(uint120).max to allow each DistributionRecord to be packed into a single storage slot. + */ + function adjust(address beneficiary, int256 amount) external onlyOwner { + DistributionRecord memory distributionRecord = records[beneficiary]; + require(distributionRecord.initialized, "must initialize before adjusting"); + + uint256 diff = uint256(amount > 0 ? amount : -amount); + require(diff < type(uint120).max, "adjustment > max uint120"); + + if (amount < 0) { + // decreasing claimable tokens + require(total >= diff, "decrease greater than distributor total"); + require(distributionRecord.total >= diff, "decrease greater than distributionRecord total"); + total -= diff; + records[beneficiary].total -= uint120(diff); + token.safeTransfer(owner(), diff); + } else { + // increasing claimable tokens + total += diff; + records[beneficiary].total += uint120(diff); + } + _reconcileVotingPower(beneficiary); + emit Adjust(beneficiary, amount); + } + + function _setToken(IERC20 _token) internal virtual { + require(address(_token) != address(0), "token is address(0)"); + token = _token; + emit SetToken(token); + } + + // Set the token being distributed + function setToken(IERC20 _token) external virtual onlyOwner { + _setToken(_token); + } + + function _setTotal(uint256 _total) internal virtual { + total = _total; + emit SetTotal(total); + } + + // Set the total to distribute + function setTotal(uint256 _total) external virtual onlyOwner { + _setTotal(_total); + } + + // Set the distributor metadata URI + function setUri(string memory _uri) external onlyOwner { + uri = _uri; + emit SetUri(uri); + } + + // set the recipient of swept funds + function setSweepRecipient(address payable _recipient) external onlyOwner { + _setSweepRecipient(_recipient); + } + + function getTotalVotes() external view returns (uint256) { + // supply of internal token used to track voting power + return totalSupply(); + } + + function getVoteFactor(address) external view returns (uint256) { + return voteFactor; + } + + /** + * @notice Set the voting power of undistributed tokens + * @param _voteFactor The voting power multiplier as a fraction of fractionDenominator + * @dev The vote factor can be any integer. If voteFactor / fractionDenominator == 1, + * one unclaimed token provides one vote. If voteFactor / fractionDenominator == 2, one + * unclaimed token counts as two votes. + */ + function setVoteFactor(uint256 _voteFactor) external onlyOwner { + voteFactor = _voteFactor; + emit SetVoteFactor(voteFactor); + } + + /** + * @dev the internal token used only for tracking voting power cannot be transferred + */ + function _approve(address, address, uint256) internal pure override { + revert("disabled for voting power"); + } + + /** + * @dev the internal token used only for tracking voting power cannot be transferred + */ + function _transfer(address, address, uint256) internal pure override { + revert("disabled for voting power"); + } +} diff --git a/zksync-ts/contracts/claim/factory/ContinuousVestingInitializable.sol b/zksync-ts/contracts/claim/factory/ContinuousVestingInitializable.sol new file mode 100644 index 00000000..8cda73d7 --- /dev/null +++ b/zksync-ts/contracts/claim/factory/ContinuousVestingInitializable.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "./AdvancedDistributorInitializable.sol"; +import {IContinuousVesting} from "../../interfaces/IContinuousVesting.sol"; + +abstract contract ContinuousVestingInitializable is Initializable, AdvancedDistributorInitializable, IContinuousVesting { + uint256 private start; // time vesting clock begins + uint256 private cliff; // time vesting begins (all tokens vested prior to the cliff are immediately claimable) + uint256 private end; // time vesting clock ends + + function __ContinuousVesting_init( + IERC20 _token, + uint256 _total, + string memory _uri, + uint256 _start, + uint256 _cliff, + uint256 _end, + uint160 _maxDelayTime, + uint160 _salt, + address _owner + ) internal onlyInitializing { + __AdvancedDistributor_init( + _token, + _total, + _uri, + 10000, // 1x voting power + 10 ** 18, // provides the highest resolution possible for continuous vesting + _maxDelayTime, + _salt, + _owner + ); + require(_start <= _cliff, "vesting cliff before start"); + require(_cliff <= _end, "vesting end before cliff"); + require(_end <= 4102444800, "vesting ends after 4102444800 (Jan 1 2100)"); + + start = _start; + cliff = _cliff; + end = _end; + + emit SetContinuousVesting(start, cliff, end); + } + + function getVestedFraction( + address beneficiary, + uint256 time // time is in seconds past the epoch (e.g. block.timestamp) + ) public view override returns (uint256) { + uint256 delayedTime = time - getFairDelayTime(beneficiary); + // no tokens are vested + if (delayedTime <= cliff) { + return 0; + } + + // all tokens are vested + if (delayedTime >= end) { + return fractionDenominator; + } + + // some tokens are vested + return (fractionDenominator * (delayedTime - start)) / (end - start); + } + + function getVestingConfig() external view returns (uint256, uint256, uint256) { + return (start, cliff, end); + } + + // Adjustable admin functions + function setVestingConfig(uint256 _start, uint256 _cliff, uint256 _end) external onlyOwner { + start = _start; + cliff = _cliff; + end = _end; + emit SetContinuousVesting(start, cliff, end); + } +} diff --git a/zksync-ts/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol b/zksync-ts/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol new file mode 100644 index 00000000..1b95d3bf --- /dev/null +++ b/zksync-ts/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "./ContinuousVestingInitializable.sol"; +import "./MerkleSetInitializable.sol"; + +contract ContinuousVestingMerkleDistributor is Initializable, ContinuousVestingInitializable, MerkleSetInitializable { + constructor() { + _disableInitializers(); + } + + function initialize( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner + ) public initializer { + __ContinuousVesting_init( + _token, _total, _uri, _start, _cliff, _end, _maxDelayTime, uint160(uint256(_merkleRoot)), _owner + ); + + __MerkleSet_init(_merkleRoot); + + _transferOwnership(_owner); + } + + function NAME() external pure override returns (string memory) { + return "ContinuousVestingMerkleInitializable"; + } + + function VERSION() external pure override returns (uint256) { + return 3; + } + + function initializeDistributionRecord( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) external validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, amount)), merkleProof) { + _initializeDistributionRecord(beneficiary, amount); + } + + function claim( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount)), merkleProof) + nonReentrant + { + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } +} diff --git a/zksync-ts/contracts/claim/factory/ContinuousVestingMerkleDistributorFactory.sol b/zksync-ts/contracts/claim/factory/ContinuousVestingMerkleDistributorFactory.sol new file mode 100644 index 00000000..7ef45fb8 --- /dev/null +++ b/zksync-ts/contracts/claim/factory/ContinuousVestingMerkleDistributorFactory.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; + +import {ContinuousVestingMerkleDistributor} from "./ContinuousVestingMerkleDistributor.sol"; + +contract ContinuousVestingMerkleDistributorFactory { + address private immutable i_implementation; + address[] public distributors; + + event DistributorDeployed(address indexed distributor); + + constructor(address implementation) { + i_implementation = implementation; + } + + function _getSalt( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + uint256 _nonce + ) private pure returns (bytes32) { + return keccak256(abi.encode( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _nonce + )); + } + + function deployDistributor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + uint256 _nonce + ) public returns (ContinuousVestingMerkleDistributor distributor) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _nonce + ); + + distributor = + ContinuousVestingMerkleDistributor(Clones.cloneDeterministic(i_implementation, salt)); + distributors.push(address(distributor)); + + emit DistributorDeployed(address(distributor)); + + distributor.initialize(_token, _total, _uri, _start, _cliff, _end, _merkleRoot, _maxDelayTime, _owner); + + return distributor; + } + + function getImplementation() public view returns (address) { + return i_implementation; + } + + function predictDistributorAddress( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + uint256 _nonce + ) public view returns (address) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _start, + _cliff, + _end, + _merkleRoot, + _maxDelayTime, + _owner, + _nonce + ); + + return Clones.predictDeterministicAddress(i_implementation, salt, address(this)); + } +} diff --git a/zksync-ts/contracts/claim/factory/DistributorInitializable.sol b/zksync-ts/contracts/claim/factory/DistributorInitializable.sol new file mode 100644 index 00000000..dae28cc7 --- /dev/null +++ b/zksync-ts/contracts/claim/factory/DistributorInitializable.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; + +import {IDistributor, DistributionRecord} from "../../interfaces/IDistributor.sol"; + +/** + * @title Distributor + * @notice Distributes funds to beneficiaries and tracks distribution status + */ +abstract contract DistributorInitializable is Initializable, IDistributor, ReentrancyGuard { + using SafeERC20 for IERC20; + + mapping(address => DistributionRecord) internal records; // track distribution records per user + IERC20 public token; // the token being claimed + uint256 public total; // total tokens allocated for claims + uint256 public claimed; // tokens already claimed + string public uri; // ipfs link on distributor info + uint256 internal fractionDenominator; // denominator for vesting fraction (e.g. if vested fraction is 100 and fractionDenominator is 10000, 1% of tokens have vested) + + // provide context on the contract name and version + function NAME() external view virtual returns (string memory); + + function VERSION() external view virtual returns (uint256); + + function __Distributor_init(IERC20 _token, uint256 _total, string memory _uri, uint256 _fractionDenominator) + internal + onlyInitializing + { + require(address(_token) != address(0), "Distributor: token is address(0)"); + require(_total > 0, "Distributor: total is 0"); + + token = _token; + total = _total; + uri = _uri; + fractionDenominator = _fractionDenominator; + emit InitializeDistributor(token, total, uri, fractionDenominator); + } + + /** + * @dev Set up the distribution record for a user. Permissions are not checked in this function. + * Amount is limited to type(uint120).max to allow each DistributionRecord to be packed into a single storage slot. + * + * @param beneficiary The address of the beneficiary + * @param _totalAmount The total amount of tokens to be distributed to the beneficiary + */ + function _initializeDistributionRecord(address beneficiary, uint256 _totalAmount) internal virtual { + // Checks + require(_totalAmount <= type(uint120).max, "Distributor: totalAmount > type(uint120).max"); + uint120 totalAmount = uint120(_totalAmount); + + // Effects - note that the existing claimed quantity is re-used during re-initialization + records[beneficiary] = DistributionRecord(true, totalAmount, records[beneficiary].claimed); + emit InitializeDistributionRecord(beneficiary, totalAmount); + } + + /** + * @notice Record the claim internally: + * @dev This function does not check permissions: caller must verify the claim is valid! + * this function should not call any untrusted external contracts to avoid reentrancy + */ + function _executeClaim(address beneficiary, uint256 _totalAmount) internal virtual returns (uint256) { + uint120 totalAmount = uint120(_totalAmount); + + // effects + if (records[beneficiary].total != totalAmount) { + // re-initialize if the total has been updated + _initializeDistributionRecord(beneficiary, totalAmount); + } + + uint120 claimableAmount = uint120(getClaimableAmount(beneficiary)); + require(claimableAmount > 0, "Distributor: no more tokens claimable right now"); + + records[beneficiary].claimed += claimableAmount; + claimed += claimableAmount; + + return claimableAmount; + } + + /** + * @dev Move tokens associated with the claim to the recipient. This function should be called + * after the claim has been executed internally to avoid reentrancy issues. + * @param _recipient The address of the recipient + * @param _amount The amount of tokens to be transferred during this claim + */ + function _settleClaim(address _recipient, uint256 _amount) internal virtual { + token.safeTransfer(_recipient, _amount); + emit Claim(_recipient, _amount); + } + + /// @notice return a distribution record + function getDistributionRecord(address beneficiary) external view virtual returns (DistributionRecord memory) { + return records[beneficiary]; + } + + // Get tokens vested as fraction of fractionDenominator + function getVestedFraction(address beneficiary, uint256 time) public view virtual returns (uint256); + + function getFractionDenominator() public view returns (uint256) { + return fractionDenominator; + } + + // get the number of tokens currently claimable by a specific use + function getClaimableAmount(address beneficiary) public view virtual returns (uint256) { + require(records[beneficiary].initialized, "Distributor: claim not initialized"); + + DistributionRecord memory record = records[beneficiary]; + + uint256 claimable = (record.total * getVestedFraction(beneficiary, block.timestamp)) / fractionDenominator; + return record.claimed >= claimable + ? 0 // no more tokens to claim + : claimable - record.claimed; // claim all available tokens + } +} diff --git a/zksync-ts/contracts/claim/factory/FairQueueInitializable.sol b/zksync-ts/contracts/claim/factory/FairQueueInitializable.sol new file mode 100644 index 00000000..0345ccea --- /dev/null +++ b/zksync-ts/contracts/claim/factory/FairQueueInitializable.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +/** + * @title FairQueue + * @notice Fairly assigns a delay time to each address from a uniform distribution over [0, maxDelayTime] + * @dev The delay is determined by calculating a distance between the user's address and a pseudorandom value based on a provided salt and a blockhash + * using the XOR distance metric. Do not use this contract if the event is public because users could grind addresses until they find one with a low delay. + */ +contract FairQueueInitializable is Initializable { + event SetDelay(uint160 maxDelayTime); + /** + * calculate a speed at which the queue is exhausted such that all users complete the queue by maxDelayTime + */ + + uint160 public distancePerSecond; + uint160 public maxDelayTime; + + /** + * @dev the random value from which a distance will be calculated for each address. Reset the random value + * to shuffle the delays for all addresses. + */ + uint160 public randomValue; + + function __FairQueue_init(uint160 _maxDelayTime, uint160 salt) internal onlyInitializing { + _setDelay(_maxDelayTime); + _setPseudorandomValue(salt); + } + + /** + * @dev internal function to set the random value. A salt (e.g. from a merkle root) is required to prevent + * naive manipulation of the random value by validators + */ + function _setPseudorandomValue(uint160 salt) internal { + if (distancePerSecond == 0) { + // there is no delay: random value is not needed + return; + } + require(salt > 0, "I demand more randomness"); + randomValue = uint160(uint256(blockhash(block.number - 1))) ^ salt; + } + + /** + * @dev Internal function to configure delay + * @param _maxDelayTime the maximum delay for any address in seconds. Set this value to 0 to disable delays entirely. + */ + function _setDelay(uint160 _maxDelayTime) internal { + maxDelayTime = _maxDelayTime; + distancePerSecond = _maxDelayTime > 0 ? type(uint160).max / _maxDelayTime : 0; + emit SetDelay(_maxDelayTime); + } + + /** + * @notice get a fixed delay for any address by drawing from a unform distribution over the interval [0, maxDelay] + * @param user The address for which a delay should be calculated. The delay is deterministic for any given address and pseudorandom value. + * @dev The delay is determined by calculating a distance between the user's address and a pseudorandom value using the XOR distance metric (c.f. Kademlia) + * + * Users cannot exploit the fair delay if: + * - The event is private, i.e. an access list of some form is required + * - Each eligible user gets exactly one address in the access list + * - There is no collusion between event participants, block validators, and event owners + * + * The threat of collusion is likely minimal: + * - the economic opportunity to validators is zero or relatively small (only specific addresses can participate in private events, and a lower delay time does not imply higher returns) + * - event owners are usually trying to achieve a fair distribution of access to their event + */ + function getFairDelayTime(address user) public view returns (uint256) { + if (distancePerSecond == 0) { + // there is no delay: all addresses may participate immediately + return 0; + } + + // calculate a distance between the random value and the user's address using the XOR distance metric (c.f. Kademlia) + uint160 distance = uint160(user) ^ randomValue; + + // return the delay (seconds) + return distance / distancePerSecond; + } +} diff --git a/zksync-ts/contracts/claim/factory/MerkleSetInitializable.sol b/zksync-ts/contracts/claim/factory/MerkleSetInitializable.sol new file mode 100644 index 00000000..a89fde96 --- /dev/null +++ b/zksync-ts/contracts/claim/factory/MerkleSetInitializable.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; + +import {IMerkleSet} from "../../interfaces/IMerkleSet.sol"; + +/** + * @title MerkleSet + * @notice Checks merkle proofs + * @dev Contracts inheriting from MerkleSet may update the merkle root whenever desired. + */ +contract MerkleSetInitializable is Initializable, IMerkleSet { + bytes32 private merkleRoot; + + function __MerkleSet_init(bytes32 _merkleRoot) internal onlyInitializing { + _setMerkleRoot(_merkleRoot); + } + + modifier validMerkleProof(bytes32 leaf, bytes32[] memory merkleProof) { + _verifyMembership(leaf, merkleProof); + + _; + } + + /** + * @notice Tests membership in the merkle set + */ + function _testMembership(bytes32 leaf, bytes32[] memory merkleProof) internal view returns (bool) { + return MerkleProof.verify(merkleProof, merkleRoot, leaf); + } + + function getMerkleRoot() public view returns (bytes32) { + return merkleRoot; + } + + /** + * @dev Verifies membership in the merkle set + */ + function _verifyMembership(bytes32 leaf, bytes32[] memory merkleProof) internal view { + require(_testMembership(leaf, merkleProof), "invalid proof"); + } + + /** + * @dev Updates the merkle root + */ + function _setMerkleRoot(bytes32 _merkleRoot) internal { + merkleRoot = _merkleRoot; + emit SetMerkleRoot(merkleRoot); + } +} diff --git a/zksync-ts/contracts/claim/factory/TrancheVestingInitializable.sol b/zksync-ts/contracts/claim/factory/TrancheVestingInitializable.sol new file mode 100644 index 00000000..c92f8144 --- /dev/null +++ b/zksync-ts/contracts/claim/factory/TrancheVestingInitializable.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "./AdvancedDistributorInitializable.sol"; +import {ITrancheVesting, Tranche} from "../../interfaces/ITrancheVesting.sol"; + +abstract contract TrancheVestingInitializable is Initializable, AdvancedDistributorInitializable, ITrancheVesting { + Tranche[] private tranches; + + function __TrancheVesting_init( + IERC20 _token, + uint256 _total, + string memory _uri, + Tranche[] memory _tranches, + uint160 _maxDelayTime, + uint160 _salt, + address _owner + ) internal onlyInitializing { + __AdvancedDistributor_init( + _token, + _total, + _uri, + 10000, // 1x voting power + 10000, // vested fraction + _maxDelayTime, + _salt, + _owner + ); + + _setTranches(_tranches); + } + + /** + * @notice Get the vested fraction for a beneficiary at a given time. + * @dev Before the first tranche time, the vested fraction will be 0. At times between + * tranche_i and tranche_i+1, the vested fraction will be tranche_i+1's vested fraction. + * After the last tranche time, the vested fraction will be the fraction denominator. + */ + function getVestedFraction(address beneficiary, uint256 time) public view override returns (uint256) { + uint256 delay = getFairDelayTime(beneficiary); + for (uint256 i = tranches.length; i != 0;) { + unchecked { + --i; + } + + if (time - delay > tranches[i].time) { + return tranches[i].vestedFraction; + } + } + + return 0; + } + + // Get a single tranche + function getTranche(uint256 i) public view returns (Tranche memory) { + return tranches[i]; + } + + // Get all tranches + function getTranches() public view returns (Tranche[] memory) { + return tranches; + } + + /** + * @dev Tranches can be updated at any time. The quantity of tokens a user can claim is based on the + * claimable quantity at the time of the claim and the quantity of tokens already claimed by the user. + */ + function _setTranches(Tranche[] memory _tranches) private { + require(_tranches.length != 0, "tranches required"); + + delete tranches; + + uint128 lastTime = 0; + uint128 lastVestedFraction = 0; + + for (uint256 i = 0; i < _tranches.length;) { + require(_tranches[i].vestedFraction != 0, "tranche vested fraction == 0"); + require(_tranches[i].time > lastTime, "tranche time must increase"); + require(_tranches[i].vestedFraction > lastVestedFraction, "tranche vested fraction must increase"); + lastTime = _tranches[i].time; + lastVestedFraction = _tranches[i].vestedFraction; + tranches.push(_tranches[i]); + + emit SetTranche(i, lastTime, lastVestedFraction); + + unchecked { + ++i; + } + } + + require(lastTime <= 4102444800, "vesting ends after 4102444800 (Jan 1 2100)"); + require(lastVestedFraction == fractionDenominator, "last tranche must vest all tokens"); + } + + /** + * @notice Set the vesting tranches. Tranches must be sorted by time and vested fraction must monotonically increase. + * The last tranche must vest all tokens (vestedFraction == fractionDenominator) + */ + function setTranches(Tranche[] memory _tranches) external onlyOwner { + _setTranches(_tranches); + } +} diff --git a/zksync-ts/contracts/claim/factory/TrancheVestingMerkleDistributor.sol b/zksync-ts/contracts/claim/factory/TrancheVestingMerkleDistributor.sol new file mode 100644 index 00000000..26b8bf03 --- /dev/null +++ b/zksync-ts/contracts/claim/factory/TrancheVestingMerkleDistributor.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "./TrancheVestingInitializable.sol"; +import "./MerkleSetInitializable.sol"; + +contract TrancheVestingMerkleDistributor is + Initializable, + TrancheVestingInitializable, + MerkleSetInitializable +{ + constructor() { + _disableInitializers(); + } + + function initialize( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner + ) public initializer { + __TrancheVesting_init(_token, _total, _uri, _tranches, _maxDelayTime, uint160(uint256(_merkleRoot)), _owner); + + __MerkleSet_init(_merkleRoot); + + _transferOwnership(_owner); + } + + function NAME() external pure override returns (string memory) { + return "TrancheVestingMerkleDistributor"; + } + + function VERSION() external pure override returns (uint256) { + return 3; + } + + function initializeDistributionRecord( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) external validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, amount)), merkleProof) { + _initializeDistributionRecord(beneficiary, amount); + } + + function claim( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount)), merkleProof) + nonReentrant + { + // effects + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } +} diff --git a/zksync-ts/contracts/claim/factory/TrancheVestingMerkleDistributorFactory.sol b/zksync-ts/contracts/claim/factory/TrancheVestingMerkleDistributorFactory.sol new file mode 100644 index 00000000..c16027c9 --- /dev/null +++ b/zksync-ts/contracts/claim/factory/TrancheVestingMerkleDistributorFactory.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Counters} from "@openzeppelin/contracts/utils/Counters.sol"; + +import {TrancheVestingMerkleDistributor, Tranche} from "./TrancheVestingMerkleDistributor.sol"; + +contract TrancheVestingMerkleDistributorFactory { + address private immutable i_implementation; + address[] public distributors; + + event DistributorDeployed(address indexed distributor); + + constructor(address implementation) { + i_implementation = implementation; + } + + function _getSalt( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + uint256 _nonce + ) private pure returns (bytes32) { + return keccak256(abi.encode( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _nonce + )); + } + + function deployDistributor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + uint256 _nonce + ) public returns (TrancheVestingMerkleDistributor distributor) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _nonce + ); + + distributor = + TrancheVestingMerkleDistributor(Clones.cloneDeterministic(i_implementation, salt)); + distributors.push(address(distributor)); + + emit DistributorDeployed(address(distributor)); + + distributor.initialize(_token, _total, _uri, _tranches, _merkleRoot, _maxDelayTime, _owner); + + return distributor; + } + + function getImplementation() public view returns (address) { + return i_implementation; + } + + function predictDistributorAddress( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + Tranche[] memory _tranches, + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + uint256 _nonce + ) public view returns (address) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _tranches, + _merkleRoot, + _maxDelayTime, + _owner, + _nonce + ); + + return Clones.predictDeterministicAddress(i_implementation, salt, address(this)); + } +} diff --git a/zksync-ts/contracts/config/INetworkConfig.sol b/zksync-ts/contracts/config/INetworkConfig.sol new file mode 100644 index 00000000..0b5a4477 --- /dev/null +++ b/zksync-ts/contracts/config/INetworkConfig.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +interface INetworkConfig { + function getFeeRecipient() external view returns (address payable); + function getStakingAddress() external view returns (address); + function getNativeTokenPriceOracleAddress() external view returns (address); + function getNativeTokenPriceOracleHeartbeat() external view returns (uint256); + function getAccessAuthorityAddress() external view returns (address); +} diff --git a/zksync-ts/contracts/config/NetworkConfig.sol b/zksync-ts/contracts/config/NetworkConfig.sol new file mode 100644 index 00000000..1e56625a --- /dev/null +++ b/zksync-ts/contracts/config/NetworkConfig.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.21; + +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "./INetworkConfig.sol"; + +contract NetworkConfig is OwnableUpgradeable, INetworkConfig { + address payable public feeRecipient; + address public stakingAddress; + bool private initialized; + address public nativeTokenPriceOracleAddress; + uint256 public nativeTokenPriceOracleHeartbeat; + address public accessAuthorityAddress; + + function initialize(address payable _feeRecipient, address _stakingAddress, address _nativeTokenPriceOracleAddress, uint256 _nativeTokenPriceOracleHeartbeat, address _accessAuthorityAddress) public initializer { + require(!initialized, "Contract instance has already been initialized"); + initialized = true; + feeRecipient = _feeRecipient; + stakingAddress = _stakingAddress; + nativeTokenPriceOracleAddress = _nativeTokenPriceOracleAddress; + nativeTokenPriceOracleHeartbeat = _nativeTokenPriceOracleHeartbeat; + accessAuthorityAddress = _accessAuthorityAddress; + __Ownable_init(); + } + + function getFeeRecipient() external view returns (address payable) { + return feeRecipient; + } + + function getStakingAddress() external view returns (address) { + return stakingAddress; + } + + function getNativeTokenPriceOracleAddress() external view returns (address) { + return nativeTokenPriceOracleAddress; + } + + function getNativeTokenPriceOracleHeartbeat() external view returns (uint256) { + return nativeTokenPriceOracleHeartbeat; + } + + function getAccessAuthorityAddress() external view returns (address) { + return accessAuthorityAddress; + } +} diff --git a/zksync-ts/contracts/erc20/MyERC20Token.sol b/zksync-ts/contracts/erc20/MyERC20Token.sol new file mode 100644 index 00000000..b97dfc0d --- /dev/null +++ b/zksync-ts/contracts/erc20/MyERC20Token.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; + +/** + * @title MyERC20Token + * @dev This is a basic ERC20 token using the OpenZeppelin's ERC20PresetFixedSupply preset. + * You can edit the default values as needed. + */ +contract MyERC20Token is ERC20Burnable { + + /** + * @dev Constructor to initialize the token with default values. + * You can edit these values as needed. + */ + constructor() ERC20("DefaultTokenName", "DTN") { + // Default initial supply of 1 million tokens (with 18 decimals) + uint256 initialSupply = 1_000_000 * (10 ** 18); + + // The initial supply is minted to the deployer's address + _mint(msg.sender, initialSupply); + } + + // Additional functions or overrides can be added here if needed. +} diff --git a/zksync-ts/contracts/governance/GovernorMultiSourceUpgradeable.sol b/zksync-ts/contracts/governance/GovernorMultiSourceUpgradeable.sol new file mode 100644 index 00000000..3a032b17 --- /dev/null +++ b/zksync-ts/contracts/governance/GovernorMultiSourceUpgradeable.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorCountingSimpleUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "./GovernorVotesMultiSourceUpgradeable.sol"; + +contract GovernorMultiSourceUpgradeable is + Initializable, + GovernorUpgradeable, + GovernorCountingSimpleUpgradeable, + GovernorVotesMultiSourceUpgradeable, + GovernorVotesQuorumFractionUpgradeable, + GovernorTimelockControlUpgradeable, + OwnableUpgradeable, + UUPSUpgradeable +{ + /** + This contract modifies OpenZeppelin's Governor to tally votes from multiple sources: + - the ERC20 voting token + - other contracts where voting power for each account monotonically decreases + + Because GovernorVotesQuorumFractionUpgradeable functionality is unchanged and vote sources may apply a voting factor other than one, + the percentage of total voting power required to reach quorum may differ somewhat from the value specified + (e.g. a 5% quorum of 1B votes is 50m votes, but if total voting power is 2B due to tokens in voting sources, the actual quorum of total voting power required to pass a proposal will be 2.5%) + */ + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize( + IVotesUpgradeable _token, + TimelockControllerUpgradeable _timelock, + IVotesUpgradeable[] calldata _voteSources + ) public virtual initializer { + __Governor_init("Governor"); + __GovernorCountingSimple_init(); + __GovernorVotesMultiSource_init(_token, _voteSources); + __GovernorVotesQuorumFraction_init(5); // the quorum numerator (5%) + __GovernorTimelockControl_init(_timelock); + __Ownable_init(); + __UUPSUpgradeable_init(); + } + + function NAME() external pure returns (string memory) { + return "GovernorMultiSourceUpgradeable"; + } + + function VERSION() external pure returns (uint256) { + return 2; + } + + function votingDelay() public pure virtual override returns (uint256) { + // return 6545; // 1 day at 12 seconds per block + return 10; // blocks + } + + function votingPeriod() public pure virtual override returns (uint256) { + // return 50400; // 1 week at 12 seconds per block + return 1000; // blocks + } + + function proposalThreshold() public pure override returns (uint256) { + return 100000000000000000000000; // 0.01% of 1B token supply (100,000 tokens) + } + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + function _getVotes( + address account, + uint256 blockNumber, + bytes memory _data + ) + internal + view + override( + // THIS LINE IS MODIFIED FROM OZ DEFAULTS + GovernorUpgradeable, + GovernorVotesUpgradeable, + GovernorVotesMultiSourceUpgradeable + ) + returns (uint256 votes) + { + return super._getVotes(account, blockNumber, _data); + } + + function quorum(uint256 blockNumber) + public + view + override(IGovernorUpgradeable, GovernorVotesQuorumFractionUpgradeable) + returns (uint256) + { + return super.quorum(blockNumber); + } + + function state(uint256 proposalId) + public + view + override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) + returns (ProposalState) + { + return super.state(proposalId); + } + + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public override(GovernorUpgradeable, IGovernorUpgradeable) returns (uint256) { + return super.propose(targets, values, calldatas, description); + } + + function _execute( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) { + super._execute(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + function _executor() + internal + view + override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) + returns (address) + { + return super._executor(); + } + + function supportsInterface(bytes4 interfaceId) + public + view + override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) + returns (bool) + { + return super.supportsInterface(interfaceId); + } +} diff --git a/zksync-ts/contracts/governance/GovernorVotesMultiSourceUpgradeable.sol b/zksync-ts/contracts/governance/GovernorVotesMultiSourceUpgradeable.sol new file mode 100644 index 00000000..9e87d7a9 --- /dev/null +++ b/zksync-ts/contracts/governance/GovernorVotesMultiSourceUpgradeable.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {GovernorUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; +import {GovernorVotesUpgradeable, IVotesUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.sol"; + +abstract contract GovernorVotesMultiSourceUpgradeable is + GovernorUpgradeable, + GovernorVotesUpgradeable +{ + // Allow governor contract to reference additional vote sources + IVotesUpgradeable[] private voteSources; + + modifier validVoteSources(IVotesUpgradeable[] calldata _voteSources) { + for (uint256 i = 0; i < _voteSources.length; ) { + require( + _voteSources[i].getPastTotalSupply(block.number - 1) > 0, + "GovernorVotesMultiSourceUpgradeable: source has no votes" + ); + unchecked { + ++i; + } + } + + _; + } + + function __GovernorVotesMultiSource_init( + IVotesUpgradeable tokenAddress, + IVotesUpgradeable[] calldata _voteSources + ) internal onlyInitializing { + __GovernorVotesMultiSource_init__unchained(tokenAddress, _voteSources); + } + + function __GovernorVotesMultiSource_init__unchained( + IVotesUpgradeable tokenAddress, + IVotesUpgradeable[] calldata _voteSources + ) internal onlyInitializing validVoteSources(_voteSources) { + super.__GovernorVotes_init_unchained(tokenAddress); + voteSources = _voteSources; + } + + /** + Modified from open zeppelin defaults + */ + function _getVotes( + address account, + uint256 blockNumber, + bytes memory _data + ) + internal + view + virtual + override(GovernorUpgradeable, GovernorVotesUpgradeable) + returns (uint256 votes) + { + // get votes from the ERC20 token + votes = super._getVotes(account, blockNumber, _data); + + // get votes from the distribution contracts + IVotesUpgradeable[] memory _voteSources = voteSources; + for (uint256 i = 0; i < _voteSources.length; ) { + votes += voteSources[i].getPastVotes(account, blockNumber); + unchecked { + ++i; + } + } + } + + /** + New function allowing the DAO to update its vote sources + */ + function setVoteSources(IVotesUpgradeable[] calldata _voteSources) + public + onlyGovernance + validVoteSources(_voteSources) + { + voteSources = _voteSources; + } + + function getVoteSources() public view returns (IVotesUpgradeable[] memory) { + return voteSources; + } + + // permit future upgrades + uint256[10] private __gap; +} diff --git a/zksync-ts/contracts/governance/TimelockControllerUpgradeable.sol b/zksync-ts/contracts/governance/TimelockControllerUpgradeable.sol new file mode 100644 index 00000000..a348636e --- /dev/null +++ b/zksync-ts/contracts/governance/TimelockControllerUpgradeable.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol"; + +contract MyTimelockControllerUpgradeable is TimelockControllerUpgradeable { + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize( + uint256 minDelay, + address[] memory proposers, + address[] memory executors + ) public initializer { + __TimelockController_init(minDelay, proposers, executors, address(0)); + } +} diff --git a/zksync-ts/contracts/interfaces/IAdjustable.sol b/zksync-ts/contracts/interfaces/IAdjustable.sol new file mode 100644 index 00000000..78f073e6 --- /dev/null +++ b/zksync-ts/contracts/interfaces/IAdjustable.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @title IAdjustable + * @dev Interface for the Adjustable contract. Defines methods to update + * the contract and events emitted upon update. + */ +interface IAdjustable { + event Adjust(address indexed beneficiary, int256 amount); + event SetToken(IERC20 indexed token); + event SetTotal(uint256 total); + event SetUri(string indexed uri); + + // Adjust the quantity claimable by a user + function adjust(address beneficiary, int256 amount) external; + + // Set the token being distributed + function setToken(IERC20 token) external; + + // Set the total distribution quantity + function setTotal(uint256 total) external; + + // Set the distributor metadata URI + function setUri(string memory uri) external; + + // Set the voting power of undistributed tokens + function setVoteFactor(uint256 setVoteFactor) external; +} diff --git a/zksync-ts/contracts/interfaces/IConnext.sol b/zksync-ts/contracts/interfaces/IConnext.sol new file mode 100644 index 00000000..ed2261e1 --- /dev/null +++ b/zksync-ts/contracts/interfaces/IConnext.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IConnext { + + function xcall( + uint32 _destination, + address _to, + address _asset, + address _delegate, + uint256 _amount, + uint256 _slippage, + bytes calldata _callData + ) external payable returns (bytes32); + + function xcall( + uint32 _destination, + address _to, + address _asset, + address _delegate, + uint256 _amount, + uint256 _slippage, + bytes calldata _callData, + uint256 _relayerFee + ) external returns (bytes32); + + function xcallIntoLocal( + uint32 _destination, + address _to, + address _asset, + address _delegate, + uint256 _amount, + uint256 _slippage, + bytes calldata _callData + ) external payable returns (bytes32); + + function domain() external view returns (uint256); +} \ No newline at end of file diff --git a/zksync-ts/contracts/interfaces/IContinuousVesting.sol b/zksync-ts/contracts/interfaces/IContinuousVesting.sol new file mode 100644 index 00000000..bd3d7f44 --- /dev/null +++ b/zksync-ts/contracts/interfaces/IContinuousVesting.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IContinuousVesting { + event SetContinuousVesting(uint256 start, uint256 cliff, uint256 end); + + function getVestingConfig() + external + view + returns ( + uint256, + uint256, + uint256 + ); + + function setVestingConfig( + uint256 _start, + uint256 _cliff, + uint256 _end + ) external; +} diff --git a/zksync-ts/contracts/interfaces/ICrosschain.sol b/zksync-ts/contracts/interfaces/ICrosschain.sol new file mode 100644 index 00000000..32d1ba02 --- /dev/null +++ b/zksync-ts/contracts/interfaces/ICrosschain.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IDistributor } from './IDistributor.sol'; +import { IXReceiver } from './IXReceiver.sol'; + +/** + * @notice Defines functions and events for receiving and tracking crosschain claims + */ +interface ICrosschain is IDistributor, IXReceiver { + /** + * @dev The beneficiary and recipient may be different addresses. The beneficiary is the address + * eligible to receive the claim, and the recipient is where the funds are actually sent. + */ + event CrosschainClaim( + bytes32 indexed id, + address indexed beneficiary, + address indexed recipient, + uint32 recipientDomain, + uint256 amount + ); +} diff --git a/zksync-ts/contracts/interfaces/IDistributor.sol b/zksync-ts/contracts/interfaces/IDistributor.sol new file mode 100644 index 00000000..a63dbd65 --- /dev/null +++ b/zksync-ts/contracts/interfaces/IDistributor.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +/** + * @dev This struct tracks claim progress for a given beneficiary. + * Because many claims are stored in a merkle root, this struct is only valid once initialized. + * Users can no longer claim once their claimed quantity meets or exceeds their total quantity. + * Note that admins may update the merkle root or adjust the total quantity for a specific + * beneficiary after initialization! + */ +struct DistributionRecord { + bool initialized; // has the claim record been initialized + uint120 total; // total token quantity claimable + uint120 claimed; // token quantity already claimed +} + +interface IDistributor { + event InitializeDistributor( + IERC20 indexed token, // the ERC20 token being distributed + uint256 total, // total distribution quantity across all beneficiaries + string uri, // a URI for additional information about the distribution + uint256 fractionDenominator // the denominator for vesting fractions represented as integers + ); + + // Fired once when a beneficiary's distribution record is set up + event InitializeDistributionRecord(address indexed beneficiary, uint256 total); + + // Fired every time a claim for a beneficiary occurs + event Claim(address indexed beneficiary, uint256 amount); + + /** + * @dev get the current distribution status for a particular user + * @param beneficiary the address of the beneficiary + */ + function getDistributionRecord( + address beneficiary + ) external view returns (DistributionRecord memory); + + /** + * @dev get the amount of tokens currently claimable by a beneficiary + * @param beneficiary the address of the beneficiary + */ + function getClaimableAmount(address beneficiary) external view returns (uint256); + + /** + * @dev get the denominator for vesting fractions represented as integers + */ + function getFractionDenominator() external view returns (uint256); + + /** + * @dev get the ERC20 token being distributed + */ + function token() external view returns (IERC20); + + /** + * @dev get the total distribution quantity across all beneficiaries + */ + function total() external view returns (uint256); + + /** + * @dev get a URI for additional information about the distribution + */ + function uri() external view returns (string memory); + + /** + * @dev get a human-readable name for the distributor that describes basic functionality + * On-chain consumers should rely on registered ERC165 interface IDs or similar for more specificity + */ + function NAME() external view returns (string memory); + + /** + * @dev get a human-readable version for the distributor that describes basic functionality + * The version should update whenever functionality significantly changes + * On-chain consumers should rely on registered ERC165 interface IDs or similar for more specificity + */ + function VERSION() external view returns (uint256); +} diff --git a/zksync-ts/contracts/interfaces/IERC20.sol b/zksync-ts/contracts/interfaces/IERC20.sol new file mode 100644 index 00000000..2786879e --- /dev/null +++ b/zksync-ts/contracts/interfaces/IERC20.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/zksync-ts/contracts/interfaces/IERC20Extended.sol b/zksync-ts/contracts/interfaces/IERC20Extended.sol new file mode 100644 index 00000000..32d8e75c --- /dev/null +++ b/zksync-ts/contracts/interfaces/IERC20Extended.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: BSL-1.1 +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IERC20Extended is IERC20 { + function decimals() external view returns (uint8); +} diff --git a/zksync-ts/contracts/interfaces/IERC20Metadata.sol b/zksync-ts/contracts/interfaces/IERC20Metadata.sol new file mode 100644 index 00000000..36f58d68 --- /dev/null +++ b/zksync-ts/contracts/interfaces/IERC20Metadata.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/zksync-ts/contracts/interfaces/IHashflowQuote.sol b/zksync-ts/contracts/interfaces/IHashflowQuote.sol new file mode 100644 index 00000000..e4d01e2d --- /dev/null +++ b/zksync-ts/contracts/interfaces/IHashflowQuote.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; +interface IHashflowQuote { + struct RFQTQuote { + address pool; + address externalAccount; + address trader; + address effectiveTrader; + address baseToken; + address quoteToken; + uint256 effectiveBaseTokenAmount; + uint256 maxBaseTokenAmount; + uint256 maxQuoteTokenAmount; + uint256 quoteExpiry; + uint256 nonce; + bytes32 txid; + bytes signature; + } + + struct XChainRFQTQuote { + uint16 srcChainId; + uint16 dstChainId; + address srcPool; + bytes32 dstPool; + address srcExternalAccount; + bytes32 dstExternalAccount; + address trader; + address baseToken; + address quoteToken; + uint256 baseTokenAmount; + uint256 quoteTokenAmount; + uint256 quoteExpiry; + uint256 nonce; + bytes32 txid; + bytes signature; + } + + enum XChainMessageProtocol { + layerZero, + wormhole + } + + function tradeSingleHop (RFQTQuote calldata quote) external payable; + + function tradeXChain ( + XChainRFQTQuote calldata quote, + XChainMessageProtocol protocol + ) external payable; +} diff --git a/zksync-ts/contracts/interfaces/IMerkleSet.sol b/zksync-ts/contracts/interfaces/IMerkleSet.sol new file mode 100644 index 00000000..b7bebd2c --- /dev/null +++ b/zksync-ts/contracts/interfaces/IMerkleSet.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.21; + +interface IMerkleSet { + event SetMerkleRoot(bytes32 merkleRoot); + + function getMerkleRoot() external view returns (bytes32 root); +} diff --git a/zksync-ts/contracts/interfaces/IOracleOrL2OracleWithSequencerCheck.sol b/zksync-ts/contracts/interfaces/IOracleOrL2OracleWithSequencerCheck.sol new file mode 100644 index 00000000..f328fef8 --- /dev/null +++ b/zksync-ts/contracts/interfaces/IOracleOrL2OracleWithSequencerCheck.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +interface IOracleOrL2OracleWithSequencerCheck { + function decimals() external view returns (uint8); + + function latestRoundData() external view returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} diff --git a/zksync-ts/contracts/interfaces/IPriceTierVesting.sol b/zksync-ts/contracts/interfaces/IPriceTierVesting.sol new file mode 100644 index 00000000..e96c5675 --- /dev/null +++ b/zksync-ts/contracts/interfaces/IPriceTierVesting.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "./IOracleOrL2OracleWithSequencerCheck.sol"; + +// time and vested fraction must monotonically increase in the tranche array +struct PriceTier { + uint128 price; // block.timestamp upon which the tranche vests + uint128 vestedFraction; // fraction of tokens unlockable +} + +interface IPriceTierVesting { + event SetPriceTierConfig( + uint256 start, + uint256 end, + IOracleOrL2OracleWithSequencerCheck oracle, + PriceTier[] tiers + ); + + function getStart() external view returns (uint256); + + function getEnd() external view returns (uint256); + + function getOracle() external view returns (IOracleOrL2OracleWithSequencerCheck); + + function getPriceTier(uint256 i) external view returns (PriceTier memory); + + function getPriceTiers() external view returns (PriceTier[] memory); + + function setPriceTiers( + uint256 _start, + uint256 _end, + IOracleOrL2OracleWithSequencerCheck _oracle, + PriceTier[] memory _tiers + ) external; +} diff --git a/zksync-ts/contracts/interfaces/ITrancheVesting.sol b/zksync-ts/contracts/interfaces/ITrancheVesting.sol new file mode 100644 index 00000000..aeb51c66 --- /dev/null +++ b/zksync-ts/contracts/interfaces/ITrancheVesting.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +struct Tranche { + uint128 time; // block.timestamp upon which the tranche vests + uint128 vestedFraction; // fraction of tokens unlockable as basis points (e.g. 100% of vested tokens is the fraction denominator, defaulting to 10000) +} + +interface ITrancheVesting { + event SetTranche(uint256 indexed index, uint128 time, uint128 VestedFraction); + + function getTranche(uint256 i) external view returns (Tranche memory); + + function getTranches() external view returns (Tranche[] memory); + + function setTranches(Tranche[] memory _tranches) external; +} diff --git a/zksync-ts/contracts/interfaces/IVesting.sol b/zksync-ts/contracts/interfaces/IVesting.sol new file mode 100644 index 00000000..a8687f79 --- /dev/null +++ b/zksync-ts/contracts/interfaces/IVesting.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +interface IVesting { + function getVestedFraction( + address, /*beneficiary*/ + uint256 time // time is in seconds past the epoch (e.g. block.timestamp) + ) external returns (uint256); +} diff --git a/zksync-ts/contracts/interfaces/IVoting.sol b/zksync-ts/contracts/interfaces/IVoting.sol new file mode 100644 index 00000000..72eeb12f --- /dev/null +++ b/zksync-ts/contracts/interfaces/IVoting.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.21; + +import { IVotes } from "@openzeppelin/contracts/governance/utils/IVotes.sol"; + +interface IVoting is IVotes { + event SetVoteFactor(uint256 voteFactor); + + // an total current voting power + function getTotalVotes() external view returns (uint256); + + // a weighting factor used to convert token holdings to voting power (eg in basis points) + function getVoteFactor(address account) external view returns (uint256); +} diff --git a/zksync-ts/contracts/interfaces/IXReceiver.sol b/zksync-ts/contracts/interfaces/IXReceiver.sol new file mode 100644 index 00000000..a1740a28 --- /dev/null +++ b/zksync-ts/contracts/interfaces/IXReceiver.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +interface IXReceiver { + function xReceive( + bytes32 _transferId, + uint256 _amount, + address _asset, + address _originSender, + uint32 _origin, + bytes memory _callData + ) external returns (bytes memory); +} diff --git a/zksync-ts/contracts/mocks/ConnextMock.sol b/zksync-ts/contracts/mocks/ConnextMock.sol new file mode 100644 index 00000000..54edaf86 --- /dev/null +++ b/zksync-ts/contracts/mocks/ConnextMock.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IXReceiver } from '../interfaces/IXReceiver.sol'; + +contract ConnextMock { + event XCalled( + uint32 destination, + address to, + address asset, + address delegate, + uint256 amount, + uint256 slippage, + bytes callData + ); + event NativeRelayerFeeIncluded(address indexed caller, uint256 amount); + uint32 private _domain; + constructor(uint32 domain_) { + setDomain(domain_); + } + + function domain() public view returns (uint32) { + return _domain; + } + + function setDomain(uint32 domain_) public { + _domain = domain_; + } + + function xcall( + uint32 _destination, + address _to, + address _asset, + address _delegate, + uint256 _amount, + uint256 _slippage, + bytes calldata _callData + ) public payable returns (bytes32) { + // Transfer asset if needed + if (_amount > 0) { + IERC20(_asset).transferFrom(msg.sender, address(this), _amount); + } + + // Emit relayer fee event for native asset + if (msg.value > 0) { + emit NativeRelayerFeeIncluded(msg.sender, msg.value); + } + + // Emit event + return identifier + emit XCalled(_destination, _to, _asset, _delegate, _amount, _slippage, _callData); + return keccak256(abi.encode(_destination, _to, _asset, _delegate, _amount, _slippage, _callData)); + } + + // call the xreceive contract pretending to be connext on the same chain + function callXreceive( + bytes32 _transferId, + uint256 _amount, + address _asset, + address _originSender, + uint32 _origin, + bytes32[] memory _proof, + address _distributor + ) public returns (bytes memory) { + + return IXReceiver(_distributor).xReceive( + _transferId, + _amount, + _asset, + _originSender, + _domain, + abi.encode(_originSender, _origin, _amount, _proof) + ); + } +} \ No newline at end of file diff --git a/zksync-ts/contracts/mocks/FakeChainlinkOracle.sol b/zksync-ts/contracts/mocks/FakeChainlinkOracle.sol new file mode 100644 index 00000000..ee94c9b5 --- /dev/null +++ b/zksync-ts/contracts/mocks/FakeChainlinkOracle.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; + +contract FakeChainlinkOracle is IOracleOrL2OracleWithSequencerCheck { + int256 private answer; + string private oracleDescription; + + constructor(int256 _answer, string memory _oracleDescription) { + answer = _answer; + oracleDescription = _oracleDescription; + } + + function decimals() external pure returns (uint8) { + return 8; + } + + function description() external view returns (string memory) { + return oracleDescription; + } + + function version() external pure returns (uint256) { + return 3; + } + + function setAnswer(int256 _answer) public { + answer = _answer; + } + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return (92233720368547777283, answer, 1644641759, 1644641759, 92233720368547777283); + } + + function getRoundData(uint80 /* _roundId */) + external + view + returns ( + uint80 roundId, + int256, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return (92233720368547777283, answer, 1644641759, 1644641759, 92233720368547777283); + } +} + +contract FakeEthOracle is IOracleOrL2OracleWithSequencerCheck { + int256 private answer; + string private oracleDescription; + + constructor(int256 _answer, string memory _oracleDescription) { + answer = _answer; + oracleDescription = _oracleDescription; + } + + function decimals() external pure returns (uint8) { + return 8; + } + + function description() external view returns (string memory) { + return oracleDescription; + } + + function version() external pure returns (uint256) { + return 3; + } + + function setAnswer(int256 _answer) public { + answer = _answer; + } + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return (92233720368547777283, answer, 1644641759, 1644641759, 92233720368547777283); + } + + function getRoundData(uint80 /* _roundId */) + external + view + returns ( + uint80 roundId, + int256, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return (92233720368547777283, answer, 1644641759, 1644641759, 92233720368547777283); + } +} + +contract FakeUsdcOracle is IOracleOrL2OracleWithSequencerCheck { + int256 private answer; + string private oracleDescription; + + constructor(int256 _answer, string memory _oracleDescription) { + answer = _answer; + oracleDescription = _oracleDescription; + } + + function decimals() external pure returns (uint8) { + return 8; + } + + function description() external view returns (string memory) { + return oracleDescription; + } + + function version() external pure returns (uint256) { + return 3; + } + + function setAnswer(int256 _answer) public { + answer = _answer; + } + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return (92233720368547777283, answer, 1644641759, 1644641759, 92233720368547777283); + } + + function getRoundData(uint80 /* _roundId */) + external + view + returns ( + uint80 roundId, + int256, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return (92233720368547777283, answer, 1644641759, 1644641759, 92233720368547777283); + } +} + +contract FakeUsdtOracle is IOracleOrL2OracleWithSequencerCheck { + int256 private answer; + string private oracleDescription; + + constructor(int256 _answer, string memory _oracleDescription) { + answer = _answer; + oracleDescription = _oracleDescription; + } + + function decimals() external pure returns (uint8) { + return 8; + } + + function description() external view returns (string memory) { + return oracleDescription; + } + + function version() external pure returns (uint256) { + return 3; + } + + function setAnswer(int256 _answer) public { + answer = _answer; + } + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return (92233720368547777283, answer, 1644641759, 1644641759, 92233720368547777283); + } + + function getRoundData(uint80 /* _roundId */) + external + view + returns ( + uint80 roundId, + int256, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return (92233720368547777283, answer, 1644641759, 1644641759, 92233720368547777283); + } +} diff --git a/zksync-ts/contracts/mocks/FakeSequencerUptimeFeed.sol b/zksync-ts/contracts/mocks/FakeSequencerUptimeFeed.sol new file mode 100644 index 00000000..165d296d --- /dev/null +++ b/zksync-ts/contracts/mocks/FakeSequencerUptimeFeed.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol"; + +contract FakeSequencerUptimeFeed is AggregatorV2V3Interface { + int256 private answer; + string private oracleDescription; + uint256 private startedAt = 1692820776; + uint256 private updatedAt = 1692820776; + + constructor(int256 _answer, string memory _oracleDescription) { + answer = _answer; + oracleDescription = _oracleDescription; + } + + function decimals() external pure returns (uint8) { + return 0; + } + + function description() external view returns (string memory) { + return oracleDescription; + } + + function version() external pure returns (uint256) { + return 1; + } + + function setAnswer(int256 _answer) public { + answer = _answer; + startedAt = block.timestamp; + updatedAt = block.timestamp; + } + + function getAnswer(uint256 /* roundId */) external view returns (int256) { + return answer; + } + + function getTimestamp(uint256 /* roundId */) external view returns (uint256) { + return startedAt; + } + + function latestAnswer() external view returns (int256) { + return answer; + } + + function latestRound() external pure returns (uint256) { + return 18446744073709552139; + } + + function latestTimestamp() external view returns (uint256) { + return startedAt; + } + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256, + uint256, + uint256, + uint80 answeredInRound + ) + { + return (18446744073709552139, answer, startedAt, updatedAt, 18446744073709552139); + } + + function getRoundData(uint80 /* roundId */) + external + view + returns ( + uint80 roundId, + int256, + uint256, + uint256, + uint80 answeredInRound + ) + { + return (18446744073709552139, answer, startedAt, updatedAt, 18446744073709552139); + } +} diff --git a/zksync-ts/contracts/mocks/GovernorMultiSourceUpgradeableMock.sol b/zksync-ts/contracts/mocks/GovernorMultiSourceUpgradeableMock.sol new file mode 100644 index 00000000..5acce857 --- /dev/null +++ b/zksync-ts/contracts/mocks/GovernorMultiSourceUpgradeableMock.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {GovernorMultiSourceUpgradeable, TimelockControllerUpgradeable, IVotesUpgradeable} from "../governance/GovernorMultiSourceUpgradeable.sol"; + +// IMPORTANT: DO NOT USE THIS CONTRACT IN PRODUCTION: THE VOTING PERIOD IS TOO SHORT! +// Change the voting delay and voting period to be shorter for testing (these are hard-coded in the real contract for gas efficiency) +contract GovernorMultiSourceUpgradeableMock is GovernorMultiSourceUpgradeable { + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize( + IVotesUpgradeable _token, + TimelockControllerUpgradeable _timelock, + IVotesUpgradeable[] calldata _voteSources + ) public override initializer { + __Governor_init("Governor"); + __GovernorCountingSimple_init(); + __GovernorVotesMultiSource_init(_token, _voteSources); + __GovernorVotesQuorumFraction_init(5); // the quorum numerator (5%) + __GovernorTimelockControl_init(_timelock); + __Ownable_init(); + __UUPSUpgradeable_init(); + } + + function votingDelay() public pure override returns (uint256) { + return 0; + } + + function votingPeriod() public pure override returns (uint256) { + // 10 blocks + return 10; + } +} diff --git a/zksync-ts/contracts/mocks/HashflowRouterMock.sol b/zksync-ts/contracts/mocks/HashflowRouterMock.sol new file mode 100644 index 00000000..deebbca3 --- /dev/null +++ b/zksync-ts/contracts/mocks/HashflowRouterMock.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IHashflowQuote} from "../interfaces/IHashflowQuote.sol"; + +// See https://docs.hashflow.com/hashflow/taker/getting-started +contract HashflowRouterMock is IHashflowQuote { + using SafeERC20 for IERC20; + constructor() {} + + // mock a fee + function estimateCrossChainFee() public pure returns (uint256) { + return 1234; + } + + function tradeSingleHop (RFQTQuote calldata quote) public payable { + // maker is a pool or separate account + address maker = quote.externalAccount == address(0) + ? quote.pool + : quote.externalAccount; + + // transfer base token to maker + if (quote.baseToken != address(0)) { + // this is an ERC20 transfer + IERC20(quote.baseToken).safeTransferFrom(msg.sender, maker, quote.effectiveBaseTokenAmount); + } else { + // this is a native token transfer + (bool success, ) = maker.call{value: quote.effectiveBaseTokenAmount}(""); + require(success, "native baseToken trade failed"); + } + + // scale the quoted token transfer based on taker order size + uint256 quoteTokenAmount = quote.maxQuoteTokenAmount * quote.effectiveBaseTokenAmount / quote.maxBaseTokenAmount; + // transfer base token to maker + if (quote.quoteToken != address(0)) { + // this is an ERC20 transfer + IERC20(quote.quoteToken).safeTransferFrom(maker, quote.trader, quoteTokenAmount); + } else { + // this is a native token transfer + // the fake Hashflow router already holds a bunch of native tokens (probably not how this works in practice) + (bool success, ) = quote.trader.call{value: quoteTokenAmount}(""); + require(success, "native quoteToken trade failed"); + } + } + + function tradeXChain ( + XChainRFQTQuote calldata quote, + XChainMessageProtocol // protocol + ) public payable { + if (quote.baseToken != address(0)) { + // this is an ERC20 traade - pay for cross-chain fee in native token + require(msg.value == estimateCrossChainFee(), "incorrect xChainFeeEstimate"); + IERC20(quote.baseToken).safeTransferFrom(msg.sender, quote.srcPool, quote.baseTokenAmount); + } else { + // this is a native trade - pay for the base token and cross-chain fee in native token + require(msg.value == estimateCrossChainFee() + quote.baseTokenAmount, "incorrect xChainFeeEstimate"); + (bool success, ) = quote.srcPool.call{value: quote.baseTokenAmount}(""); + require(success, "x-chain native trade failed"); + } + // The other half of the trade occurs on another chain + } + + // fake - give maker a way to settle in ETH + function depositEth() external payable {} +} + diff --git a/zksync-ts/contracts/mocks/OracleMock.sol b/zksync-ts/contracts/mocks/OracleMock.sol new file mode 100644 index 00000000..435524c5 --- /dev/null +++ b/zksync-ts/contracts/mocks/OracleMock.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; + +contract OracleMock is IOracleOrL2OracleWithSequencerCheck { + function decimals() external pure returns (uint8) { + return 8; + } + + function latestRoundData() + external + pure + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + return ( + 18446744073709590880, + 294670000000, + 1720387410, + 1720387410, + 18446744073709590880 + ); + } +} diff --git a/zksync-ts/contracts/mocks/SoftMfersMock.sol b/zksync-ts/contracts/mocks/SoftMfersMock.sol new file mode 100644 index 00000000..773bfc72 --- /dev/null +++ b/zksync-ts/contracts/mocks/SoftMfersMock.sol @@ -0,0 +1,8 @@ +// spdx-license-identifier: mit +pragma solidity 0.8.21; + +contract SoftMfersMock { + function redeem(address receiver, uint256 amount) public { + // no-op + } +} diff --git a/zksync-ts/contracts/nft/MyNFT.sol b/zksync-ts/contracts/nft/MyNFT.sol new file mode 100644 index 00000000..654dc45b --- /dev/null +++ b/zksync-ts/contracts/nft/MyNFT.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/Counters.sol"; + +/** + * @title MyNFT + * @dev Basic ERC721 token with auto-incrementing token IDs. + * The owner can mint new tokens. Token URIs are autogenerated based on a base URI. + */ +contract MyNFT is ERC721Enumerable, Ownable { + using Counters for Counters.Counter; + Counters.Counter private _tokenIdTracker; + string private _baseTokenURI; + + constructor(string memory name, string memory symbol, string memory baseTokenURI) ERC721(name, symbol) { + _baseTokenURI = baseTokenURI; + } + + function _baseURI() internal view virtual override returns (string memory) { + return _baseTokenURI; + } + + /** + * @dev Mints a new token to the specified address. + * Only the owner can mint new tokens. + * @param to The address that will receive the minted token. + */ + function mint(address to) external onlyOwner { + _tokenIdTracker.increment(); + _mint(to, _tokenIdTracker.current()); + } + + + // Additional functions or overrides can be added here if needed. +} diff --git a/zksync-ts/contracts/paymasters/ApprovalPaymaster.sol b/zksync-ts/contracts/paymasters/ApprovalPaymaster.sol new file mode 100644 index 00000000..9b4ddf01 --- /dev/null +++ b/zksync-ts/contracts/paymasters/ApprovalPaymaster.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol"; +import {IPaymasterFlow} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol"; +import {TransactionHelper, Transaction} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; + +import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +/// @author Matter Labs +/// @notice This smart contract pays the gas fees for accounts with balance of a specific ERC20 token. It makes use of the approval-based flow paymaster. +contract ApprovalPaymaster is IPaymaster, Ownable { + uint256 constant PRICE_FOR_PAYING_FEES = 1; + + address public allowedToken; + + modifier onlyBootloader() { + require( + msg.sender == BOOTLOADER_FORMAL_ADDRESS, + "Only bootloader can call this method" + ); + // Continue execution if called from the bootloader. + _; + } + + constructor(address _erc20) { + allowedToken = _erc20; + } + + function validateAndPayForPaymasterTransaction( + bytes32, + bytes32, + Transaction calldata _transaction + ) + external + payable + onlyBootloader + returns (bytes4 magic, bytes memory context) + { + // By default we consider the transaction as accepted. + magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; + require( + _transaction.paymasterInput.length >= 4, + "The standard paymaster input must be at least 4 bytes long" + ); + + bytes4 paymasterInputSelector = bytes4( + _transaction.paymasterInput[0:4] + ); + // Approval based flow + if (paymasterInputSelector == IPaymasterFlow.approvalBased.selector) { + // While the transaction data consists of address, uint256 and bytes data, + // the data is not needed for this paymaster + (address token, uint256 amount, bytes memory data) = abi.decode( + _transaction.paymasterInput[4:], + (address, uint256, bytes) + ); + + // Verify if token is the correct one + require(token == allowedToken, "Invalid token"); + + // We verify that the user has provided enough allowance + address userAddress = address(uint160(_transaction.from)); + + address thisAddress = address(this); + + uint256 providedAllowance = IERC20(token).allowance( + userAddress, + thisAddress + ); + require( + providedAllowance >= PRICE_FOR_PAYING_FEES, + "Min allowance too low" + ); + + // Note, that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit, + // neither paymaster nor account are allowed to access this context variable. + uint256 requiredETH = _transaction.gasLimit * + _transaction.maxFeePerGas; + + try + IERC20(token).transferFrom(userAddress, thisAddress, amount) + {} catch (bytes memory revertReason) { + // If the revert reason is empty or represented by just a function selector, + // we replace the error with a more user-friendly message + if (revertReason.length <= 4) { + revert("Failed to transferFrom from users' account"); + } else { + assembly { + revert(add(0x20, revertReason), mload(revertReason)) + } + } + } + + // The bootloader never returns any data, so it can safely be ignored here. + (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ + value: requiredETH + }(""); + require( + success, + "Failed to transfer tx fee to the bootloader. Paymaster balance might not be enough." + ); + } else { + revert("Unsupported paymaster flow"); + } + } + + function postTransaction( + bytes calldata _context, + Transaction calldata _transaction, + bytes32, + bytes32, + ExecutionResult _txResult, + uint256 _maxRefundedGas + ) external payable override onlyBootloader {} + + function withdraw(address _to) external onlyOwner { + (bool success, ) = payable(_to).call{value: address(this).balance}(""); + require(success, "Failed to withdraw funds from paymaster."); + } + + receive() external payable {} +} diff --git a/zksync-ts/contracts/paymasters/GeneralPaymaster.sol b/zksync-ts/contracts/paymasters/GeneralPaymaster.sol new file mode 100644 index 00000000..b261187b --- /dev/null +++ b/zksync-ts/contracts/paymasters/GeneralPaymaster.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol"; +import {IPaymasterFlow} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol"; +import {TransactionHelper, Transaction} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; + +import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +/// @author Matter Labs +/// @notice This contract does not include any validations other than using the paymaster general flow. +contract GeneralPaymaster is IPaymaster, Ownable { + modifier onlyBootloader() { + require( + msg.sender == BOOTLOADER_FORMAL_ADDRESS, + "Only bootloader can call this method" + ); + // Continue execution if called from the bootloader. + _; + } + + function validateAndPayForPaymasterTransaction( + bytes32, + bytes32, + Transaction calldata _transaction + ) + external + payable + onlyBootloader + returns (bytes4 magic, bytes memory context) + { + // By default we consider the transaction as accepted. + magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; + require( + _transaction.paymasterInput.length >= 4, + "The standard paymaster input must be at least 4 bytes long" + ); + + bytes4 paymasterInputSelector = bytes4( + _transaction.paymasterInput[0:4] + ); + if (paymasterInputSelector == IPaymasterFlow.general.selector) { + // Note, that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit, + // neither paymaster nor account are allowed to access this context variable. + uint256 requiredETH = _transaction.gasLimit * + _transaction.maxFeePerGas; + + // The bootloader never returns any data, so it can safely be ignored here. + (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ + value: requiredETH + }(""); + require( + success, + "Failed to transfer tx fee to the Bootloader. Paymaster balance might not be enough." + ); + } else { + revert("Unsupported paymaster flow in paymasterParams."); + } + } + + function postTransaction( + bytes calldata _context, + Transaction calldata _transaction, + bytes32, + bytes32, + ExecutionResult _txResult, + uint256 _maxRefundedGas + ) external payable override onlyBootloader {} + + function withdraw(address payable _to) external onlyOwner { + uint256 balance = address(this).balance; + (bool success, ) = _to.call{value: balance}(""); + require(success, "Failed to withdraw funds from paymaster."); + } + + receive() external payable {} +} diff --git a/zksync-ts/contracts/payment/Payment.sol b/zksync-ts/contracts/payment/Payment.sol new file mode 100644 index 00000000..99766e15 --- /dev/null +++ b/zksync-ts/contracts/payment/Payment.sol @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; +// pragma abicoder v2; + +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../utilities/Sweepable.sol"; +import "../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; + +// Metrics are only updated by the buyWithToken() and buyWithNative() functions +struct Metrics { + // number of purchases + uint256 purchaseCount; + // number of buyers + uint256 buyerCount; + // amount bought denominated in a base currency + uint256 purchaseTotal; + // amount bought for each user denominated in a base currency + mapping(address => uint256) buyerTotal; +} + +struct PaymentTokenInfo { + IOracleOrL2OracleWithSequencerCheck oracle; + uint8 decimals; +} + +// contract Payment is Sweepable { +// using SafeERC20 for IERC20; + +// event Initialize(string baseCurrency, IOracleOrL2OracleWithSequencerCheck nativeOracle, bool nativePaymentsEnabled); +// event SetPaymentTokenInfo(IERC20 token, PaymentTokenInfo paymentTokenInfo); + +// // All chainlink oracles used must have 8 decimals! +// uint256 constant public BASE_CURRENCY_DECIMALS = 8; +// // All supported chains must use 18 decimals (e.g. 1e18 wei / eth) +// uint256 constant internal NATIVE_TOKEN_DECIMALS = 18; +// // the base currency being used, e.g. 'USD' +// string public baseCurrency; + +// string public constant NAME = 'Payment'; +// uint public constant VERSION = 1; + +// // / price, e.g. ETH/USD price +// IOracleOrL2OracleWithSequencerCheck public nativeTokenPriceOracle; + +// // whether native payments are enabled (set during intialization) +// bool nativePaymentsEnabled; + +// // / price oracles, eg USDC address => ETH/USDC price +// mapping(IERC20 => PaymentTokenInfo) public paymentTokens; + +// // derived from payments +// Metrics public metrics; + +// // All clones will share the information in the implementation constructor +// constructor( +// address payable recipient +// ) { +// feeRecipient = _feeRecipient; +// feeBips = _feeBips; + +// emit ImplementationConstructor(feeRecipient, feeBips); +// } + +// /** +// Replacement for constructor for clones of the implementation contract +// Important: anyone can call the initialize function! +// */ +// function initialize( +// address _owner, +// Config calldata _config, +// string calldata _baseCurrency, +// bool _nativePaymentsEnabled, +// IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, +// IERC20Upgradeable[] calldata tokens, +// IOracleOrL2OracleWithSequencerCheck[] calldata oracles, +// uint8[] calldata decimals +// ) public initializer validUpdate(_config) { +// // initialize the PullPayment escrow contract +// __PullPayment_init(); + +// // validate the new sale +// require(tokens.length == oracles.length, "token and oracle lengths !="); +// require(tokens.length == decimals.length, "token and decimals lengths !="); +// require(address(_nativeTokenPriceOracle) != address(0), "native oracle == 0"); +// require(_nativeTokenPriceOracle.decimals() == BASE_CURRENCY_DECIMALS, "native oracle decimals != 8"); + +// // save the new sale +// config = _config; + +// // save payment config +// baseCurrency = _baseCurrency; +// nativeTokenPriceOracle = _nativeTokenPriceOracle; +// nativePaymentsEnabled = _nativePaymentsEnabled; +// emit Initialize(config, baseCurrency, nativeTokenPriceOracle, _nativePaymentsEnabled); + +// for (uint i = 0; i < tokens.length; i++) { +// // double check that tokens and oracles are real addresses +// require(address(tokens[i]) != address(0), "payment token == 0"); +// require(address(oracles[i]) != address(0), "token oracle == 0"); +// // Double check that oracles use the expected 8 decimals +// require(oracles[i].decimals() == BASE_CURRENCY_DECIMALS, "token oracle decimals != 8"); +// // save the payment token info +// paymentTokens[tokens[i]] = PaymentTokenInfo({ +// oracle: oracles[i], +// decimals: decimals[i] +// }); + +// emit SetPaymentTokenInfo(tokens[i], paymentTokens[tokens[i]]); +// } + +// // Set the random value for the fair queue time +// randomValue = generatePseudorandomValue(config.merkleRoot); + +// // transfer ownership to the user initializing the sale +// _transferOwnership(_owner); +// } + +// /** +// Check that the user can currently participate in the sale based on the merkle root + +// Merkle root options: +// - bytes32(0): this is a public sale, any address can participate +// - otherwise: this is a private sale, users must submit a merkle proof that their address is included in the merkle root +// */ +// modifier canAccessSale(bytes calldata data, bytes32[] calldata proof) { +// // make sure the buyer is an EOA +// // TODO: Review this check for meta-transactions +// require((_msgSender() == tx.origin), "Must buy with an EOA"); + +// // If the merkle root is non-zero this is a private sale and requires a valid proof +// if (config.merkleRoot == bytes32(0)) { +// // this is a public sale +// // IMPORTANT: data is only validated if the merkle root is checked! Public sales do not check any merkle roots! +// require(data.length == 0, "data not permitted on public sale"); +// } else { +// // this is a private sale +// require( +// this.isValidMerkleProof( +// config.merkleRoot, +// _msgSender(), +// data, +// proof +// ) == true, +// "bad merkle proof for sale" +// ); +// } + +// // Require the sale to be open +// require(block.timestamp > config.startTime, "sale has not started yet"); +// require(block.timestamp < config.endTime, "sale has ended"); +// require(metrics.purchaseTotal < config.saleMaximum, "sale buy limit reached"); + +// // Reduce congestion by randomly assigning each user a delay time in a virtual queue based on comparing their address and a random value +// // if config.maxQueueTime == 0 the delay is 0 +// require(block.timestamp - config.startTime > getFairQueueTime(_msgSender()), "not your turn yet"); +// _; +// } + +// /** +// Check that the new sale is a valid update +// - If the config already exists, it must not be over (cannot edit sale after it concludes) +// - Sale start, end, and max queue times must be consistent and not too far in the future +// */ +// modifier validUpdate(Config calldata newConfig) { +// // get the existing config +// Config memory oldConfig = config; + +// /** +// - @notice - Block updates after sale is over +// - @dev - Since validUpdate is called by initialize(), we can have a new +// - sale here, identifiable by default randomValue of 0 +// */ +// if (randomValue != 0) { +// // this is an existing sale: cannot update after it has ended +// require(block.timestamp < oldConfig.endTime, "sale is over: cannot upate"); +// if (block.timestamp > oldConfig.startTime) { +// // the sale has already started, some things should not be edited +// require(oldConfig.saleMaximum == newConfig.saleMaximum, "editing saleMaximum after sale start"); +// } +// } + +// // the total sale limit must be at least as large as the per-user limit + +// // all required values must be present and reasonable +// // check if the caller accidentally entered a value in milliseconds instead of seconds +// require(newConfig.startTime <= 4102444800, "start > 4102444800 (Jan 1 2100)"); +// require(newConfig.endTime <= 4102444800, "end > 4102444800 (Jan 1 2100)"); +// require(newConfig.maxQueueTime <= 604800, "max queue time > 604800 (1 week)"); +// require(newConfig.recipient != address(0), "recipient == address(0)"); + +// // sale, user, and purchase limits must be compatible +// require(newConfig.saleMaximum > 0, "saleMaximum == 0"); +// require(newConfig.userMaximum > 0, "userMaximum == 0"); +// require(newConfig.userMaximum <= newConfig.saleMaximum, "userMaximum > saleMaximum"); +// require(newConfig.purchaseMinimum <= newConfig.userMaximum, "purchaseMinimum > userMaximum"); + +// // new sale times must be internally consistent +// require(newConfig.startTime + newConfig.maxQueueTime < newConfig.endTime, "sale must be open for at least maxQueueTime"); + +// _; +// } + +// modifier validPaymentToken(IERC20Upgradeable token) { +// // check that this token is configured as a payment method +// PaymentTokenInfo memory info = paymentTokens[token]; +// require(address(info.oracle) != address(0), "invalid payment token"); + +// _; +// } + +// modifier areNativePaymentsEnabled() { +// require(nativePaymentsEnabled, "native payments disabled"); + +// _; +// } + +// // Get info on a payment token +// function getPaymentToken(IERC20Upgradeable token) external view returns (PaymentTokenInfo memory) { +// return paymentTokens[token]; +// } + +// // Get a positive token price from a chainlink oracle +// function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle) public view returns (uint) { +// ( +// uint80 roundID, +// int _price, +// uint startedAt, +// uint timeStamp, +// uint80 answeredInRound +// ) = oracle.latestRoundData(); + +// require(_price > 0, "negative price"); +// require(answeredInRound > 0, "answer == 0"); +// require(timeStamp > 0, "round not complete"); +// require(answeredInRound >= roundID, "stale price"); + +// return uint(_price); +// } + +// /** +// Generate a pseudorandom value +// This is not a truly random value: +// - miners can alter the block hash +// - owners can repeatedly call setMerkleRoot() +// - owners can choose when to submit the transaction +// */ +// function generatePseudorandomValue(bytes32 merkleRoot) public view returns(uint160) { +// return uint160(uint256(blockhash(block.number - 1))) ^ uint160(uint256(merkleRoot)); +// } + +// /** +// Get the delay in seconds that a specific buyer must wait after the sale begins in order to buy tokens in the sale + +// Buyers cannot exploit the fair queue when: +// - The sale is private (merkle root != bytes32(0)) +// - Each eligible buyer gets exactly one address in the merkle root + +// Although miners and sellers can minimize the delay for an arbitrary address, these are not significant threats: +// - the economic opportunity to miners is zero or relatively small (only specific addresses can participate in private sales, and a better queue postion does not imply high returns) +// - sellers can repeatedly set merkle roots to achieve a favorable queue time for any address, but sellers already control the tokens being sold! +// */ +// function getFairQueueTime(address buyer) public view returns(uint) { +// if (config.maxQueueTime == 0) { +// // there is no delay: all addresses may participate immediately +// return 0; +// } + +// // calculate a distance between the random value and the user's address using the XOR distance metric (c.f. Kademlia) +// uint160 distance = uint160(buyer) ^ randomValue; + +// // calculate a speed at which the queue is exhausted such that all users complete the queue by sale.maxQueueTime +// uint160 distancePerSecond = type(uint160).max / uint160(config.maxQueueTime); +// // return the delay (seconds) +// return distance / distancePerSecond; +// } + +// /** +// Convert a token quantity (e.g. USDC or ETH) to a base currency (e.g. USD) with the same number of decimals as the price oracle (e.g. 8) + +// Example: given 2 NCT tokens, each worth $1.23, tokensToBaseCurrency should return 246000000 ($2.46) + +// Function arguments +// - tokenQuantity: 2000000000000000000 +// - tokenDecimals: 18 + +// NCT/USD chainlink oracle (important! the oracle must be / not /, e.g. ETH/USD, ~$2000 not USD/ETH, ~0.0005) +// - baseCurrencyPerToken: 123000000 +// - baseCurrencyDecimals: 8 + +// Calculation: 2000000000000000000 * 123000000 / 1000000000000000000 + +// Returns: 246000000 +// */ +// // function tokensToBaseCurrency(SafeERC20Upgradeable token, uint256 quantity) public view validPaymentToken(token) returns (uint256) { +// // PaymentTokenInfo info = paymentTokens[token]; +// // return quantity * getOraclePrice(info.oracle) / (10 ** info.decimals); +// // } +// function tokensToBaseCurrency(uint256 tokenQuantity, uint256 tokenDecimals, IOracleOrL2OracleWithSequencerCheck oracle) public view returns (uint256 value) { +// return tokenQuantity * getOraclePrice(oracle) / (10 ** tokenDecimals); +// } + +// function total() external override view returns(uint256) { +// return metrics.purchaseTotal; +// } + +// function isOver() public override view returns(bool) { +// return config.endTime <= block.timestamp || metrics.purchaseTotal >= config.saleMaximum; +// } + +// function isOpen() public override view returns(bool) { +// return config.startTime < block.timestamp && config.endTime > block.timestamp && metrics.purchaseTotal < config.saleMaximum; +// } + +// // return the amount bought by this user in base currency +// function buyerTotal(address user) external override view returns(uint256) { +// return metrics.buyerTotal[user]; +// } + +// /** +// Records a purchase +// Follow the Checks -> Effects -> Interactions pattern +// * Checks: CALLER MUST ENSURE BUYER IS PERMITTED TO PARTICIPATE IN THIS SALE: THIS METHOD DOES NOT CHECK WHETHER THE BUYER SHOULD BE ABLE TO ACCESS THE SALE! +// * Effects: record the payment +// * Interactions: none! +// */ +// function _execute(uint256 baseCurrencyQuantity, bytes calldata data) internal { +// // Checks +// uint256 userLimit = config.userMaximum; + +// if (data.length > 0) { +// require(uint8(bytes1(data[0:1])) == PER_USER_PURCHASE_LIMIT, "unknown data"); +// require(data.length == 33, "data length != 33 bytes"); +// userLimit = uint256(bytes32(data[1:33])); +// } + +// require( +// baseCurrencyQuantity + metrics.buyerTotal[_msgSender()] <= userLimit, +// "purchase exceeds your limit" +// ); + +// require( +// baseCurrencyQuantity + metrics.purchaseTotal <= config.saleMaximum, +// "purchase exceeds sale limit" +// ); + +// require( +// baseCurrencyQuantity >= config.purchaseMinimum, +// "purchase under minimum" +// ); + +// // Effects +// metrics.purchaseCount += 1; +// if (metrics.buyerTotal[_msgSender()] == 0) { +// // if no prior purchases, this is a new buyer +// metrics.buyerCount += 1; +// } +// metrics.purchaseTotal += baseCurrencyQuantity; +// metrics.buyerTotal[_msgSender()] += baseCurrencyQuantity; +// } + +// /** +// Settle payment made with payment token +// Important: this function has no checks! Only call if the purchase is valid! +// */ +// function _settlePaymentToken(uint256 baseCurrencyValue, IERC20Upgradeable token, uint256 quantity) internal { +// uint256 fee = 0; +// if (feeBips > 0) { +// fee = (quantity * feeBips) / fractionDenominator; +// token.safeTransferFrom(_msgSender(), feeRecipient, fee); +// } +// token.safeTransferFrom(_msgSender(), address(this), quantity - fee); +// emit Buy(_msgSender(), address(token), baseCurrencyValue, quantity, fee); +// } + +// /** +// Settle payment made with native token +// Important: this function has no checks! Only call if the purchase is valid! +// */ +// function _settleNativeToken(uint256 baseCurrencyValue, uint256 nativeTokenQuantity) internal { +// uint256 nativeFee = 0; +// if (feeBips > 0) { +// nativeFee = (nativeTokenQuantity * feeBips) / fractionDenominator; +// _asyncTransfer(feeRecipient, nativeFee); +// } +// _asyncTransfer(config.recipient, nativeFee); +// // This contract will hold the native token until claimed by the owner +// emit Buy(_msgSender(), address(0), baseCurrencyValue, nativeTokenQuantity, nativeFee); +// } + +// /** +// Pay with the payment token (e.g. USDC) +// */ +// function buyWithToken( +// IERC20Upgradeable token, +// uint256 quantity, +// bytes calldata data, +// bytes32[] calldata proof +// ) external override canAccessSale(data, proof) validPaymentToken(token) nonReentrant { +// // convert to base currency from native tokens +// PaymentTokenInfo memory tokenInfo = paymentTokens[token]; +// uint256 baseCurrencyValue = tokensToBaseCurrency(quantity, tokenInfo.decimals, tokenInfo.oracle); +// // Checks and Effects +// _execute(baseCurrencyValue, data); +// // Interactions +// _settlePaymentToken(baseCurrencyValue, token, quantity); +// } + +// /** +// Pay with the native token (e.g. ETH) +// */ +// function buyWithNative( +// bytes calldata data, +// bytes32[] calldata proof +// ) external override payable canAccessSale(data, proof) areNativePaymentsEnabled nonReentrant { +// // convert to base currency from native tokens +// uint256 baseCurrencyValue = tokensToBaseCurrency(msg.value, NATIVE_TOKEN_DECIMALS, nativeTokenPriceOracle); +// // Checks and Effects +// _execute(baseCurrencyValue, data); +// // Interactions +// _settleNativeToken(baseCurrencyValue, msg.value); +// } + +// /** +// External management functions (only the owner may update the sale) +// */ +// function update(Config calldata _config) external validUpdate(_config) onlyOwner { +// config = _config; +// // updates always reset the random value +// randomValue = generatePseudorandomValue(config.merkleRoot); +// emit Update(config); +// } + +// // Tell users where they can claim tokens +// function registerDistributor(address _distributor) external onlyOwner { +// require(_distributor != address(0), "Distributor == address(0)"); +// distributor = _distributor; +// emit RegisterDistributor(distributor); +// } + +// /** +// Public management functions +// */ +// // Sweep an ERC20 token to the recipient (public function) +// function sweepToken(IERC20Upgradeable token) external { +// uint256 amount = token.balanceOf(address(this)); +// token.safeTransfer(config.recipient, amount); +// emit SweepToken(address(token), amount); +// } + +// // sweep native token to the recipient (public function) +// function sweepNative() external { +// uint256 amount = address(this).balance; +// (bool success, ) = config.recipient.call{value: amount}(""); +// require(success, "Transfer failed."); +// emit SweepNative(amount); +// } +// } diff --git a/zksync-ts/contracts/sale/v1.3/ISaleManager.sol b/zksync-ts/contracts/sale/v1.3/ISaleManager.sol new file mode 100644 index 00000000..a4c479e5 --- /dev/null +++ b/zksync-ts/contracts/sale/v1.3/ISaleManager.sol @@ -0,0 +1,97 @@ +pragma solidity >=0.8.0 <0.9.0; + +// SPDX-License-Identifier: MIT + +interface ISaleManager_v_1_3 { + function getAdmin(bytes32 saleId) external view returns (address); + + function getRecipient(bytes32 saleId) external view returns (address); + + function getMerkleRoot(bytes32 saleId) external view returns (bytes32); + + function getPriceOracle() external view returns (address); + + function getClaimManager(bytes32 saleId) external view returns (address); + + function getSaleBuyLimit(bytes32 saleId) external view returns (uint256); + + function getUserBuyLimit(bytes32 saleId) external view returns (uint256); + + function getPurchaseMinimum(bytes32 saleId) external view returns (uint256); + + function getStartTime(bytes32 saleId) external view returns (uint256); + + function getEndTime(bytes32 saleId) external view returns (uint256); + + function getUri(bytes32 saleId) external view returns (string memory); + + function getPrice(bytes32 saleId) external view returns (uint256); + + function getDecimals(bytes32 saleId) external view returns (uint256); + + function getTotalSpent(bytes32 saleId) external view returns (uint256); + + function getRandomValue(bytes32 saleId) external view returns (uint160); + + function getMaxQueueTime(bytes32 saleId) external view returns (uint256); + + function generateRandomishValue(bytes32 merkleRoot) external view returns (uint160); + + function getFairQueueTime(bytes32 saleId, address buyer) external view returns (uint256); + + function spentToBought(bytes32 saleId, uint256 spent) external view returns (uint256); + + function nativeToPaymentToken(uint256 nativeValue) external view returns (uint256); + + function getSpent(bytes32 saleId, address userAddress) external view returns (uint256); + + function getBought(bytes32 saleId, address userAddress) external view returns (uint256); + + function isOpen(bytes32 saleId) external view returns (bool); + + function isOver(bytes32 saleId) external view returns (bool); + + function newSale( + address payable recipient, + bytes32 merkleRoot, + uint256 saleBuyLimit, + uint256 userBuyLimit, + uint256 purchaseMinimum, + uint256 startTime, + uint256 endTime, + uint160 maxQueueTime, + string memory uri, + uint256 price, + uint8 decimals + ) external returns (bytes32); + + function setStart(bytes32 saleId, uint256 startTime) external; + + function setEnd(bytes32 saleId, uint256 endTime) external; + + function setMerkleRoot(bytes32 saleId, bytes32 merkleRoot) external; + + function setMaxQueueTime(bytes32 saleId, uint160 maxQueueTime) external; + + function setUriAndMerkleRoot( + bytes32 saleId, + bytes32 merkleRoot, + string calldata uri + ) external; + + function buy( + bytes32 saleId, + uint256 tokenQuantity, + bytes32[] calldata proof + ) external; + + function buy(bytes32 saleId, bytes32[] calldata proof) external payable; + + function registerClaimManager(bytes32 saleId, address claimManager) external; + + function recoverERC20( + bytes32 saleId, + address tokenAddress, + uint256 tokenAmount + ) external; +} diff --git a/zksync-ts/contracts/sale/v1.3/SaleManager.sol b/zksync-ts/contracts/sale/v1.3/SaleManager.sol new file mode 100644 index 00000000..3bf263bb --- /dev/null +++ b/zksync-ts/contracts/sale/v1.3/SaleManager.sol @@ -0,0 +1,560 @@ +pragma solidity >=0.8.0 <0.9.0; +// SPDX-License-Identifier: MIT + +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/security/PullPayment.sol"; +import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import "./ISaleManager.sol"; + +contract SaleManager_v_1_3 is ReentrancyGuard, PullPayment, ISaleManager_v_1_3 { + using SafeERC20 for IERC20; + + IOracleOrL2OracleWithSequencerCheck priceOracle; + IERC20 public immutable paymentToken; + uint8 public immutable paymentTokenDecimals; + + struct Sale { + address payable recipient; // the address that will receive sale proceeds + address admin; // the address administering the sale + bytes32 merkleRoot; // the merkle root used for proving access + address claimManager; // address where purchased tokens can be claimed (optional) + uint256 saleBuyLimit; // max tokens that can be spent in total + uint256 userBuyLimit; // max tokens that can be spent per user + uint256 purchaseMinimum; // minimum tokens that can be spent per purchase + uint256 startTime; // the time at which the sale starts (seconds past the epoch) + uint256 endTime; // the time at which the sale will end, regardless of tokens raised (seconds past the epoch) + string uri; // reference to off-chain sale configuration (e.g. IPFS URI) + uint256 price; // the price of the asset (eg if 1.0 NCT == $1.23 of USDC: 1230000) + uint8 decimals; // the number of decimals in the asset being sold, e.g. 18 + uint256 totalSpent; // total purchases denominated in payment token + uint256 maxQueueTime; // what is the maximum length of time a user could wait in the queue after the sale starts? + uint160 randomValue; // reasonably random value: xor of merkle root and blockhash for transaction setting merkle root + mapping(address => uint256) spent; + } + + // this struct has two many members for a public getter + mapping(bytes32 => Sale) private sales; + + // global metrics + uint256 public saleCount = 0; + uint256 public totalSpent = 0; + + // public version + string public constant VERSION = "1.3"; + + event NewSale( + bytes32 indexed saleId, + bytes32 indexed merkleRoot, + address indexed recipient, + address admin, + uint256 saleBuyLimit, + uint256 userBuyLimit, + uint256 purchaseMinimum, + uint256 maxQueueTime, + uint256 startTime, + uint256 endTime, + string uri, + uint256 price, + uint8 decimals + ); + + event Deploy(address paymentToken, uint8 paymentTokenDecimals, address priceOracle); + event UpdateStart(bytes32 indexed saleId, uint256 startTime); + event UpdateEnd(bytes32 indexed saleId, uint256 endTime); + event UpdateMerkleRoot(bytes32 indexed saleId, bytes32 merkleRoot); + event UpdateMaxQueueTime(bytes32 indexed saleId, uint256 maxQueueTime); + event Buy( + bytes32 indexed saleId, + address indexed buyer, + uint256 value, + bool native, + bytes32[] proof + ); + event RegisterClaimManager(bytes32 indexed saleId, address indexed claimManager); + event UpdateUri(bytes32 indexed saleId, string uri); + + constructor( + address _paymentToken, + uint8 _paymentTokenDecimals, + address _priceOracle + ) payable { + paymentToken = IERC20(_paymentToken); + paymentTokenDecimals = _paymentTokenDecimals; + priceOracle = IOracleOrL2OracleWithSequencerCheck(_priceOracle); + emit Deploy(_paymentToken, _paymentTokenDecimals, _priceOracle); + } + + modifier validSale(bytes32 saleId) { + // if the admin is address(0) there is no sale struct at this saleId + require(sales[saleId].admin != address(0), "invalid sale id"); + _; + } + + modifier isAdmin(bytes32 saleId) { + // msg.sender is never address(0) so this handles uninitialized sales + require(sales[saleId].admin == msg.sender, "must be admin"); + _; + } + + modifier canAccessSale(bytes32 saleId, bytes32[] calldata proof) { + // make sure the buyer is an EOA + require((msg.sender == tx.origin), "Must buy with an EOA"); + + // If the merkle root is non-zero this is a private sale and requires a valid proof + if (sales[saleId].merkleRoot != bytes32(0)) { + require( + this._isAllowed(sales[saleId].merkleRoot, msg.sender, proof) == true, + "bad merkle proof for sale" + ); + } + + // Reduce congestion by randomly assigning each user a delay time in a virtual queue based on comparing their address and a random value + // if sale.maxQueueTime == 0 the delay is 0 + require( + block.timestamp - sales[saleId].startTime > getFairQueueTime(saleId, msg.sender), + "not your turn yet" + ); + + _; + } + + modifier requireOpen(bytes32 saleId) { + require(block.timestamp > sales[saleId].startTime, "sale not started yet"); + require(block.timestamp < sales[saleId].endTime, "sale ended"); + require(sales[saleId].totalSpent < sales[saleId].saleBuyLimit, "sale over"); + _; + } + + // Get current price from chainlink oracle + function getLatestPrice() public view returns (uint256) { + ( + uint80 roundID, + int256 price, + uint256 startedAt, + uint256 timeStamp, + uint80 answeredInRound + ) = priceOracle.latestRoundData(); + + require(price > 0, "negative price"); + return uint256(price); + } + + // Accessor functions + function getAdmin(bytes32 saleId) public view validSale(saleId) returns (address) { + return (sales[saleId].admin); + } + + function getRecipient(bytes32 saleId) public view validSale(saleId) returns (address) { + return (sales[saleId].recipient); + } + + function getMerkleRoot(bytes32 saleId) public view validSale(saleId) returns (bytes32) { + return (sales[saleId].merkleRoot); + } + + function getPriceOracle() public view returns (address) { + return address(priceOracle); + } + + function getClaimManager(bytes32 saleId) public view validSale(saleId) returns (address) { + return (sales[saleId].claimManager); + } + + function getSaleBuyLimit(bytes32 saleId) public view validSale(saleId) returns (uint256) { + return (sales[saleId].saleBuyLimit); + } + + function getUserBuyLimit(bytes32 saleId) public view validSale(saleId) returns (uint256) { + return (sales[saleId].userBuyLimit); + } + + function getPurchaseMinimum(bytes32 saleId) public view validSale(saleId) returns (uint256) { + return (sales[saleId].purchaseMinimum); + } + + function getStartTime(bytes32 saleId) public view validSale(saleId) returns (uint256) { + return (sales[saleId].startTime); + } + + function getEndTime(bytes32 saleId) public view validSale(saleId) returns (uint256) { + return (sales[saleId].endTime); + } + + function getUri(bytes32 saleId) public view validSale(saleId) returns (string memory) { + return sales[saleId].uri; + } + + function getPrice(bytes32 saleId) public view validSale(saleId) returns (uint256) { + return (sales[saleId].price); + } + + function getDecimals(bytes32 saleId) public view validSale(saleId) returns (uint256) { + return (sales[saleId].decimals); + } + + function getTotalSpent(bytes32 saleId) public view validSale(saleId) returns (uint256) { + return (sales[saleId].totalSpent); + } + + function getRandomValue(bytes32 saleId) public view validSale(saleId) returns (uint160) { + return sales[saleId].randomValue; + } + + function getMaxQueueTime(bytes32 saleId) public view validSale(saleId) returns (uint256) { + return sales[saleId].maxQueueTime; + } + + function generateRandomishValue(bytes32 merkleRoot) public view returns (uint160) { + /** + Generate a randomish numeric value in the range [0, 2 ^ 160 - 1] + + This is not a truly random value: + - miners can alter the previous block's hash by holding the transaction in the mempool + - admins can choose when to submit the transaction + - admins can repeatedly call setMerkleRoot() + */ + return uint160(uint256(blockhash(block.number - 1))) ^ uint160(uint256(merkleRoot)); + } + + function getFairQueueTime(bytes32 saleId, address buyer) + public + view + validSale(saleId) + returns (uint256) + { + /** + Get the delay in seconds that a specific buyer must wait after the sale begins in order to buy tokens in the sale + + Buyers cannot exploit the fair queue when: + - The sale is private (merkle root != bytes32(0)) + - Each eligible buyer gets exactly one address in the merkle root + + Although miners and admins can minimize the delay for an arbitrary address, these are not significant threats + - the economic opportunity to miners is zero or relatively small (only specific addresses can participate in private sales, and a better queue postion does not imply high returns) + - admins can repeatedly set merkle roots (but admins already control the tokens being sold!) + + */ + if (sales[saleId].maxQueueTime == 0) { + // there is no delay: all addresses may participate immediately + return 0; + } + + // calculate a distance between the random value and the user's address using the XOR distance metric (c.f. Kademlia) + uint160 distance = uint160(buyer) ^ sales[saleId].randomValue; + + // calculate a speed at which the queue is exhausted such that all users complete the queue by sale.maxQueueTime + uint160 distancePerSecond = type(uint160).max / uint160(sales[saleId].maxQueueTime); + // return the delay (seconds) + return distance / distancePerSecond; + } + + function spentToBought(bytes32 saleId, uint256 spent) public view returns (uint256) { + // Convert tokens spent (e.g. 10,000,000 USDC = $10) to tokens bought (e.g. 8.13e18) at a price of $1.23/NCT + // convert an integer value of tokens spent to an integer value of tokens bought + return (spent * 10**sales[saleId].decimals) / (sales[saleId].price); + } + + function nativeToPaymentToken(uint256 nativeValue) public view returns (uint256) { + // convert a payment in the native token (eg ETH) to an integer value of the payment token + return + (nativeValue * getLatestPrice() * 10**paymentTokenDecimals) / + (10**(priceOracle.decimals() + 18)); + } + + function getSpent(bytes32 saleId, address userAddress) + public + view + validSale(saleId) + returns (uint256) + { + // returns the amount spent by this user in paymentToken + return (sales[saleId].spent[userAddress]); + } + + function getBought(bytes32 saleId, address userAddress) + public + view + validSale(saleId) + returns (uint256) + { + // returns the amount bought by this user in the new token being sold + return (spentToBought(saleId, sales[saleId].spent[userAddress])); + } + + function isOpen(bytes32 saleId) public view validSale(saleId) returns (bool) { + // is the sale currently open? + return (block.timestamp > sales[saleId].startTime && + block.timestamp < sales[saleId].endTime && + sales[saleId].totalSpent < sales[saleId].saleBuyLimit); + } + + function isOver(bytes32 saleId) public view validSale(saleId) returns (bool) { + // is the sale permanently over? + return (block.timestamp >= sales[saleId].endTime || + sales[saleId].totalSpent >= sales[saleId].saleBuyLimit); + } + + /** + sale setup and config + - the address calling this method is the admin: only the admin can change sale configuration + - all payments are sent to the the recipient + */ + function newSale( + address payable recipient, + bytes32 merkleRoot, + uint256 saleBuyLimit, + uint256 userBuyLimit, + uint256 purchaseMinimum, + uint256 startTime, + uint256 endTime, + uint160 maxQueueTime, + string memory uri, + uint256 price, + uint8 decimals + ) public returns (bytes32) { + require(recipient != address(0), "recipient must not be zero address"); + require(startTime <= 4102444800, "max: 4102444800 (Jan 1 2100)"); + require(endTime <= 4102444800, "max: 4102444800 (Jan 1 2100)"); + require(startTime < endTime, "sale must start before it ends"); + require(endTime > block.timestamp, "sale must end in future"); + require(userBuyLimit <= saleBuyLimit, "userBuyLimit cannot exceed saleBuyLimit"); + require(purchaseMinimum <= userBuyLimit, "purchaseMinimum cannot exceed userBuyLimit"); + require(userBuyLimit > 0, "userBuyLimit must be > 0"); + require(saleBuyLimit > 0, "saleBuyLimit must be > 0"); + require(endTime - startTime > maxQueueTime, "sale must be open for longer than max queue time"); + + // Generate a reorg-resistant sale ID + bytes32 saleId = keccak256( + abi.encodePacked( + merkleRoot, + recipient, + saleBuyLimit, + userBuyLimit, + purchaseMinimum, + startTime, + endTime, + uri, + price, + decimals + ) + ); + + // This ensures the Sale struct wasn't already created (msg.sender will never be the zero address) + require(sales[saleId].admin == address(0), "a sale with these parameters already exists"); + + Sale storage s = sales[saleId]; + + s.merkleRoot = merkleRoot; + s.admin = msg.sender; + s.recipient = recipient; + s.saleBuyLimit = saleBuyLimit; + s.userBuyLimit = userBuyLimit; + s.purchaseMinimum = purchaseMinimum; + s.startTime = startTime; + s.endTime = endTime; + s.price = price; + s.decimals = decimals; + s.uri = uri; + s.maxQueueTime = maxQueueTime; + s.randomValue = generateRandomishValue(merkleRoot); + + saleCount++; + + emit NewSale( + saleId, + s.merkleRoot, + s.recipient, + s.admin, + s.saleBuyLimit, + s.userBuyLimit, + s.purchaseMinimum, + s.maxQueueTime, + s.startTime, + s.endTime, + s.uri, + s.price, + s.decimals + ); + + return saleId; + } + + function setStart(bytes32 saleId, uint256 startTime) public validSale(saleId) isAdmin(saleId) { + // admin can update start time until the sale starts + require(block.timestamp < sales[saleId].endTime, "disabled after sale close"); + require(startTime < sales[saleId].endTime, "sale start must precede end"); + require(startTime <= 4102444800, "max: 4102444800 (Jan 1 2100)"); + require( + sales[saleId].endTime - startTime > sales[saleId].maxQueueTime, + "sale must be open for longer than max queue time" + ); + + sales[saleId].startTime = startTime; + emit UpdateStart(saleId, startTime); + } + + function setEnd(bytes32 saleId, uint256 endTime) public validSale(saleId) isAdmin(saleId) { + // admin can update end time until the sale ends + require(block.timestamp < sales[saleId].endTime, "disabled after sale closes"); + require(endTime > block.timestamp, "sale must end in future"); + require(endTime <= 4102444800, "max: 4102444800 (Jan 1 2100)"); + require(sales[saleId].startTime < endTime, "sale must start before it ends"); + require( + endTime - sales[saleId].startTime > sales[saleId].maxQueueTime, + "sale must be open for longer than max queue time" + ); + + sales[saleId].endTime = endTime; + emit UpdateEnd(saleId, endTime); + } + + function setMerkleRoot(bytes32 saleId, bytes32 merkleRoot) + public + validSale(saleId) + isAdmin(saleId) + { + require(!isOver(saleId), "cannot set merkle root once sale is over"); + sales[saleId].merkleRoot = merkleRoot; + sales[saleId].randomValue = generateRandomishValue(merkleRoot); + emit UpdateMerkleRoot(saleId, merkleRoot); + } + + function setMaxQueueTime(bytes32 saleId, uint160 maxQueueTime) + public + validSale(saleId) + isAdmin(saleId) + { + // the queue time may be adjusted after the sale begins + require( + sales[saleId].endTime > block.timestamp, + "cannot adjust max queue time after sale ends" + ); + sales[saleId].maxQueueTime = maxQueueTime; + emit UpdateMaxQueueTime(saleId, maxQueueTime); + } + + function setUriAndMerkleRoot( + bytes32 saleId, + bytes32 merkleRoot, + string calldata uri + ) public validSale(saleId) isAdmin(saleId) { + sales[saleId].uri = uri; + setMerkleRoot(saleId, merkleRoot); + emit UpdateUri(saleId, uri); + } + + function _isAllowed( + bytes32 root, + address account, + bytes32[] calldata proof + ) external pure returns (bool) { + // check if the account is in the merkle tree + bytes32 leaf = keccak256(abi.encodePacked(account)); + if (MerkleProof.verify(proof, root, leaf)) { + return true; + } + return false; + } + + // pay with the payment token (eg USDC) + function buy( + bytes32 saleId, + uint256 tokenQuantity, + bytes32[] calldata proof + ) public validSale(saleId) requireOpen(saleId) canAccessSale(saleId, proof) nonReentrant { + // make sure the purchase would not break any sale limits + require(tokenQuantity >= sales[saleId].purchaseMinimum, "purchase below minimum"); + + require( + tokenQuantity + sales[saleId].spent[msg.sender] <= sales[saleId].userBuyLimit, + "purchase exceeds your limit" + ); + + require( + tokenQuantity + sales[saleId].totalSpent <= sales[saleId].saleBuyLimit, + "purchase exceeds sale limit" + ); + + require( + paymentToken.allowance(msg.sender, address(this)) >= tokenQuantity, + "allowance too low" + ); + + // move the funds + paymentToken.safeTransferFrom(msg.sender, sales[saleId].recipient, tokenQuantity); + + // effects after interaction: we need a reentrancy guard + sales[saleId].spent[msg.sender] += tokenQuantity; + sales[saleId].totalSpent += tokenQuantity; + totalSpent += tokenQuantity; + + emit Buy(saleId, msg.sender, tokenQuantity, false, proof); + } + + // pay with the native token + function buy(bytes32 saleId, bytes32[] calldata proof) + public + payable + validSale(saleId) + requireOpen(saleId) + canAccessSale(saleId, proof) + nonReentrant + { + // convert to the equivalent payment token value from wei + uint256 tokenQuantity = nativeToPaymentToken(msg.value); + + // make sure the purchase would not break any sale limits + require(tokenQuantity >= sales[saleId].purchaseMinimum, "purchase below minimum"); + + require( + tokenQuantity + sales[saleId].spent[msg.sender] <= sales[saleId].userBuyLimit, + "purchase exceeds your limit" + ); + + require( + tokenQuantity + sales[saleId].totalSpent <= sales[saleId].saleBuyLimit, + "purchase exceeds sale limit" + ); + + // Forward eth to PullPayment escrow for withdrawal to recipient + /** + * @dev OZ PullPayment._asyncTransfer + * @dev Called by the payer to store the sent amount as credit to be pulled. + * Funds sent in this way are stored in an intermediate {Escrow} contract, + * so there is no danger of them being spent before withdrawal. + * + * @param dest The destination address of the funds. + * @param amount The amount to transfer. + */ + _asyncTransfer(getRecipient(saleId), msg.value); + + // account for the purchase in equivalent payment token value + sales[saleId].spent[msg.sender] += tokenQuantity; + sales[saleId].totalSpent += tokenQuantity; + totalSpent += tokenQuantity; + + // flag this payment as using the native token + emit Buy(saleId, msg.sender, tokenQuantity, true, proof); + } + + // Tell users where they can claim tokens + function registerClaimManager(bytes32 saleId, address claimManager) + public + validSale(saleId) + isAdmin(saleId) + { + require(claimManager != address(0), "Claim manager must be a non-zero address"); + sales[saleId].claimManager = claimManager; + emit RegisterClaimManager(saleId, claimManager); + } + + function recoverERC20( + bytes32 saleId, + address tokenAddress, + uint256 tokenAmount + ) public isAdmin(saleId) { + IERC20(tokenAddress).transfer(getRecipient(saleId), tokenAmount); + } +} diff --git a/zksync-ts/contracts/sale/v2.1/FlatPriceSale.sol b/zksync-ts/contracts/sale/v2.1/FlatPriceSale.sol new file mode 100644 index 00000000..31c91ec4 --- /dev/null +++ b/zksync-ts/contracts/sale/v2.1/FlatPriceSale.sol @@ -0,0 +1,575 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; +// pragma abicoder v2; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/PullPaymentUpgradeable.sol"; +import "./Sale.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +/** +Allow qualified users to participate in a sale according to sale rules. + +Management +- the address that deploys the sale is the sale owner +- owners may change some sale parameters (e.g. start and end times) +- sale proceeds are sent to the sale recipient + +Qualification +- public sale: anyone can participate +- private sale: only users who can prove membership in a merkle tree can participate + +Sale Rules +- timing: purchases can only be made + - after the sale opens + - after the per-account random queue time has elapsed + - before the sale ends +- purchase quantity: quantity is limited by + - per-address limit + - total sale limit +- payment method: participants can pay using either + - the native token on the network (e.g. ETH) + - a single ERC-20 token (e.g. USDC) +- number of purchases: there is no limit to the number of compliant purchases a user may make + +Token Distribution +- this contract does not distribute any purchased tokens + +Metrics +- purchase count: number of purchases made in this sale +- user count: number of unique addresses that participated in this sale +- total bought: value of purchases denominated in a base currency (e.g. USD) as an integer (to get the float value, divide by oracle decimals) +- bought per user: value of a user's purchases denominated in a base currency (e.g. USD) + +total bought and bought per user metrics are inclusive of any fee charged (if a fee is charged, the sale recipient will receive less than the total spend) +*/ + +// Sale can only be updated post-initialization by the contract owner! +struct Config { + // the address that will receive sale proceeds (tokens and native) minus any fees sent to the fee recipient + address payable recipient; + // the merkle root used for proving access + bytes32 merkleRoot; + // max that can be spent in the sale in the base currency + uint256 saleMaximum; + // max that can be spent per user in the base currency + uint256 userMaximum; + // minimum that can be bought in a specific purchase + uint256 purchaseMinimum; + // the time at which the sale starts (users will have an additional random delay if maxQueueTime is set) + uint256 startTime; + // the time at which the sale will end, regardless of tokens raised + uint256 endTime; + // what is the maximum length of time a user could wait in the queue after the sale starts? + uint256 maxQueueTime; + // a link to off-chain information about this sale + string URI; +} + +// Metrics are only updated by the buyWithToken() and buyWithNative() functions +struct Metrics { + // number of purchases + uint256 purchaseCount; + // number of buyers + uint256 buyerCount; + // amount bought denominated in a base currency + uint256 purchaseTotal; + // amount bought for each user denominated in a base currency + mapping(address => uint256) buyerTotal; +} + +struct PaymentTokenInfo { + IOracleOrL2OracleWithSequencerCheck oracle; + uint8 decimals; +} + +contract FlatPriceSale_v_2_1 is Sale, PullPaymentUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + + event ImplementationConstructor(address payable indexed feeRecipient, uint256 feeBips); + event Update(Config config); + event Initialize( + Config config, + string baseCurrency, + IOracleOrL2OracleWithSequencerCheck nativeOracle, + bool nativePaymentsEnabled + ); + event SetPaymentTokenInfo(IERC20Upgradeable token, PaymentTokenInfo paymentTokenInfo); + event SweepToken(address indexed token, uint256 amount); + event SweepNative(uint256 amount); + event RegisterDistributor(address distributor); + + // All supported chains must use 18 decimals (e.g. 1e18 wei / eth) + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + // flag for additional merkle root data + uint8 internal constant PER_USER_PURCHASE_LIMIT = 1; + uint8 internal constant PER_USER_END_TIME = 2; + + /** + Variables set by implementation contract constructor (immutable) + */ + + // a fee may be charged by the sale manager + uint256 immutable feeBips; + uint256 constant fractionDenominator = 10000; + + // the recipient of the fee + address payable immutable feeRecipient; + + // an optional address where buyers can receive distributed tokens + address distributor; + + /** + Variables set during initialization of clone contracts ("immutable" on each instance) + */ + + // the base currency being used, e.g. 'USD' + string public baseCurrency; + + string public constant VERSION = "2.2"; + + // / price, e.g. ETH/USD price + IOracleOrL2OracleWithSequencerCheck public nativeTokenPriceOracle; + + // whether native payments are enabled (set during intialization) + bool nativePaymentsEnabled; + + // / price oracles, eg USDC address => ETH/USDC price + mapping(IERC20Upgradeable => PaymentTokenInfo) public paymentTokens; + + // owner can update these + Config public config; + + // derived from payments + Metrics public metrics; + + // reasonably random value: xor of merkle root and blockhash for transaction setting merkle root + uint160 internal randomValue; + + // All clones will share the information in the implementation constructor + constructor(uint256 _feeBips, address payable _feeRecipient) { + if (_feeBips > 0) { + require(_feeRecipient != address(0), "feeRecipient == 0"); + } + feeRecipient = _feeRecipient; + feeBips = _feeBips; + + emit ImplementationConstructor(feeRecipient, feeBips); + } + + /** + Replacement for constructor for clones of the implementation contract + Important: anyone can call the initialize function! + */ + function initialize( + address _owner, + Config calldata _config, + string calldata _baseCurrency, + bool _nativePaymentsEnabled, + IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, + IERC20Upgradeable[] calldata tokens, + IOracleOrL2OracleWithSequencerCheck[] calldata oracles, + uint8[] calldata decimals + ) public initializer validUpdate(_config) { + // initialize the PullPayment escrow contract + __PullPayment_init(); + + // validate the new sale + require(tokens.length == oracles.length, "token and oracle lengths !="); + require(tokens.length == decimals.length, "token and decimals lengths !="); + require(address(_nativeTokenPriceOracle) != address(0), "native oracle == 0"); + + // save the new sale + config = _config; + + // save payment config + baseCurrency = _baseCurrency; + nativeTokenPriceOracle = _nativeTokenPriceOracle; + nativePaymentsEnabled = _nativePaymentsEnabled; + emit Initialize(config, baseCurrency, nativeTokenPriceOracle, _nativePaymentsEnabled); + + for (uint256 i = 0; i < tokens.length; i++) { + // double check that tokens and oracles are real addresses + require(address(tokens[i]) != address(0), "payment token == 0"); + require(address(oracles[i]) != address(0), "token oracle == 0"); + // save the payment token info + paymentTokens[tokens[i]] = PaymentTokenInfo({ oracle: oracles[i], decimals: decimals[i] }); + + emit SetPaymentTokenInfo(tokens[i], paymentTokens[tokens[i]]); + } + + // Set the random value for the fair queue time + randomValue = generatePseudorandomValue(config.merkleRoot); + + // transfer ownership to the user initializing the sale + _transferOwnership(_owner); + } + + /** + Check that the user can currently participate in the sale based on the merkle root + + Merkle root options: + - bytes32(0): this is a public sale, any address can participate + - otherwise: this is a private sale, users must submit a merkle proof that their address is included in the merkle root + */ + modifier canAccessSale(bytes calldata data, bytes32[] calldata proof) { + // make sure the buyer is an EOA + // TODO: Review this check for meta-transactions + require((_msgSender() == tx.origin), "Must buy with an EOA"); + + // If the merkle root is non-zero this is a private sale and requires a valid proof + if (config.merkleRoot == bytes32(0)) { + // this is a public sale + // IMPORTANT: data is only validated if the merkle root is checked! Public sales do not check any merkle roots! + require(data.length == 0, "data not permitted on public sale"); + } else { + // this is a private sale + require( + this.isValidMerkleProof(config.merkleRoot, _msgSender(), data, proof) == true, + "bad merkle proof for sale" + ); + } + + // Require the sale to be open + require(block.timestamp > config.startTime, "sale has not started yet"); + require(block.timestamp < config.endTime, "sale has ended"); + require(metrics.purchaseTotal < config.saleMaximum, "sale buy limit reached"); + + // Reduce congestion by randomly assigning each user a delay time in a virtual queue based on comparing their address and a random value + // if config.maxQueueTime == 0 the delay is 0 + require( + block.timestamp - config.startTime > getFairQueueTime(_msgSender()), + "not your turn yet" + ); + _; + } + + /** + Check that the new sale is a valid update + - If the config already exists, it must not be over (cannot edit sale after it concludes) + - Sale start, end, and max queue times must be consistent and not too far in the future + */ + modifier validUpdate(Config calldata newConfig) { + // get the existing config + Config memory oldConfig = config; + + /** + - @notice - Block updates after sale is over + - @dev - Since validUpdate is called by initialize(), we can have a new + - sale here, identifiable by default randomValue of 0 + */ + if (randomValue != 0) { + // this is an existing sale: cannot update after it has ended + require(block.timestamp < oldConfig.endTime, "sale is over: cannot upate"); + if (block.timestamp > oldConfig.startTime) { + // the sale has already started, some things should not be edited + require( + oldConfig.saleMaximum == newConfig.saleMaximum, + "editing saleMaximum after sale start" + ); + } + } + + // the total sale limit must be at least as large as the per-user limit + + // all required values must be present and reasonable + // check if the caller accidentally entered a value in milliseconds instead of seconds + require(newConfig.startTime <= 4102444800, "start > 4102444800 (Jan 1 2100)"); + require(newConfig.endTime <= 4102444800, "end > 4102444800 (Jan 1 2100)"); + require(newConfig.maxQueueTime <= 604800, "max queue time > 604800 (1 week)"); + require(newConfig.recipient != address(0), "recipient == address(0)"); + + // sale, user, and purchase limits must be compatible + require(newConfig.saleMaximum > 0, "saleMaximum == 0"); + require(newConfig.userMaximum > 0, "userMaximum == 0"); + require(newConfig.userMaximum <= newConfig.saleMaximum, "userMaximum > saleMaximum"); + require(newConfig.purchaseMinimum <= newConfig.userMaximum, "purchaseMinimum > userMaximum"); + + // new sale times must be internally consistent + require( + newConfig.startTime + newConfig.maxQueueTime < newConfig.endTime, + "sale must be open for at least maxQueueTime" + ); + + _; + } + + modifier validPaymentToken(IERC20Upgradeable token) { + // check that this token is configured as a payment method + PaymentTokenInfo memory info = paymentTokens[token]; + require(address(info.oracle) != address(0), "invalid payment token"); + + _; + } + + modifier areNativePaymentsEnabled() { + require(nativePaymentsEnabled, "native payments disabled"); + + _; + } + + // Get info on a payment token + function getPaymentToken(IERC20Upgradeable token) + external + view + returns (PaymentTokenInfo memory) + { + return paymentTokens[token]; + } + + // Get a positive token price from a chainlink oracle + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + /* uint256 startedAt */, + uint256 timeStamp, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(timeStamp > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + + return uint256(_price); + } + + /** + Generate a pseudorandom value + This is not a truly random value: + - miners can alter the block hash + - owners can repeatedly call setMerkleRoot() + - owners can choose when to submit the transaction + */ + function generatePseudorandomValue(bytes32 merkleRoot) public view returns (uint160) { + return uint160(uint256(blockhash(block.number - 1))) ^ uint160(uint256(merkleRoot)); + } + + /** + Get the delay in seconds that a specific buyer must wait after the sale begins in order to buy tokens in the sale + + Buyers cannot exploit the fair queue when: + - The sale is private (merkle root != bytes32(0)) + - Each eligible buyer gets exactly one address in the merkle root + + Although miners and sellers can minimize the delay for an arbitrary address, these are not significant threats: + - the economic opportunity to miners is zero or relatively small (only specific addresses can participate in private sales, and a better queue postion does not imply high returns) + - sellers can repeatedly set merkle roots to achieve a favorable queue time for any address, but sellers already control the tokens being sold! + */ + function getFairQueueTime(address buyer) public view returns (uint256) { + if (config.maxQueueTime == 0) { + // there is no delay: all addresses may participate immediately + return 0; + } + + // calculate a distance between the random value and the user's address using the XOR distance metric (c.f. Kademlia) + uint160 distance = uint160(buyer) ^ randomValue; + + // calculate a speed at which the queue is exhausted such that all users complete the queue by sale.maxQueueTime + uint160 distancePerSecond = type(uint160).max / uint160(config.maxQueueTime); + // return the delay (seconds) + return distance / distancePerSecond; + } + + /** + Convert a token quantity (e.g. USDC or ETH) to a base currency (e.g. USD) with the same number of decimals as the price oracle (e.g. 8) + + Example: given 2 NCT tokens, each worth $1.23, tokensToBaseCurrency should return 246000000 ($2.46) + + Function arguments + - tokenQuantity: 2000000000000000000 + - tokenDecimals: 18 + + NCT/USD chainlink oracle (important! the oracle must be / not /, e.g. ETH/USD, ~$2000 not USD/ETH, ~0.0005) + - baseCurrencyPerToken: 123000000 + - baseCurrencyDecimals: 8 + + Calculation: 2000000000000000000 * 123000000 / 1000000000000000000 + + Returns: 246000000 + */ + // function tokensToBaseCurrency(SafeERC20Upgradeable token, uint256 quantity) public view validPaymentToken(token) returns (uint256) { + // PaymentTokenInfo info = paymentTokens[token]; + // return quantity * getOraclePrice(info.oracle) / (10 ** info.decimals); + // } + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle)) / (10**tokenDecimals); + } + + function total() external view override returns (uint256) { + return metrics.purchaseTotal; + } + + function isOver() public view override returns (bool) { + return config.endTime <= block.timestamp || metrics.purchaseTotal >= config.saleMaximum; + } + + function isOpen() public view override returns (bool) { + return + config.startTime < block.timestamp && + config.endTime > block.timestamp && + metrics.purchaseTotal < config.saleMaximum; + } + + // return the amount bought by this user in base currency + function buyerTotal(address user) external view override returns (uint256) { + return metrics.buyerTotal[user]; + } + + /** + Records a purchase + Follow the Checks -> Effects -> Interactions pattern + * Checks: CALLER MUST ENSURE BUYER IS PERMITTED TO PARTICIPATE IN THIS SALE: THIS METHOD DOES NOT CHECK WHETHER THE BUYER SHOULD BE ABLE TO ACCESS THE SALE! + * Effects: record the payment + * Interactions: none! + */ + function _execute(uint256 baseCurrencyQuantity, bytes calldata data) internal { + // Checks + uint256 userLimit = config.userMaximum; + + if (data.length > 0) { + require(uint8(bytes1(data[0:1])) == PER_USER_PURCHASE_LIMIT, "unknown data"); + require(data.length == 33, "data length != 33 bytes"); + userLimit = uint256(bytes32(data[1:33])); + } + + require( + baseCurrencyQuantity + metrics.buyerTotal[_msgSender()] <= userLimit, + "purchase exceeds your limit" + ); + + require( + baseCurrencyQuantity + metrics.purchaseTotal <= config.saleMaximum, + "purchase exceeds sale limit" + ); + + require(baseCurrencyQuantity >= config.purchaseMinimum, "purchase under minimum"); + + // Effects + metrics.purchaseCount += 1; + if (metrics.buyerTotal[_msgSender()] == 0) { + // if no prior purchases, this is a new buyer + metrics.buyerCount += 1; + } + metrics.purchaseTotal += baseCurrencyQuantity; + metrics.buyerTotal[_msgSender()] += baseCurrencyQuantity; + } + + /** + Settle payment made with payment token + Important: this function has no checks! Only call if the purchase is valid! + */ + function _settlePaymentToken( + uint256 baseCurrencyValue, + IERC20Upgradeable token, + uint256 quantity + ) internal { + uint256 fee = 0; + if (feeBips > 0) { + fee = (quantity * feeBips) / fractionDenominator; + token.safeTransferFrom(_msgSender(), feeRecipient, fee); + } + token.safeTransferFrom(_msgSender(), address(this), quantity - fee); + emit Buy(_msgSender(), address(token), baseCurrencyValue, quantity, fee); + } + + /** + Settle payment made with native token + Important: this function has no checks! Only call if the purchase is valid! + */ + function _settleNativeToken(uint256 baseCurrencyValue, uint256 nativeTokenQuantity) internal { + uint256 nativeFee = 0; + if (feeBips > 0) { + nativeFee = (nativeTokenQuantity * feeBips) / fractionDenominator; + _asyncTransfer(feeRecipient, nativeFee); + } + _asyncTransfer(config.recipient, nativeTokenQuantity - nativeFee); + // This contract will hold the native token until claimed by the owner + emit Buy(_msgSender(), address(0), baseCurrencyValue, nativeTokenQuantity, nativeFee); + } + + /** + Pay with the payment token (e.g. USDC) + */ + function buyWithToken( + IERC20Upgradeable token, + uint256 quantity, + bytes calldata data, + bytes32[] calldata proof + ) external override canAccessSale(data, proof) validPaymentToken(token) nonReentrant { + // convert to base currency from native tokens + PaymentTokenInfo memory tokenInfo = paymentTokens[token]; + uint256 baseCurrencyValue = tokensToBaseCurrency( + quantity, + tokenInfo.decimals, + tokenInfo.oracle + ); + // Checks and Effects + _execute(baseCurrencyValue, data); + // Interactions + _settlePaymentToken(baseCurrencyValue, token, quantity); + } + + /** + Pay with the native token (e.g. ETH) + */ + function buyWithNative(bytes calldata data, bytes32[] calldata proof) + external + payable + override + canAccessSale(data, proof) + areNativePaymentsEnabled + nonReentrant + { + // convert to base currency from native tokens + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle + ); + // Checks and Effects + _execute(baseCurrencyValue, data); + // Interactions + _settleNativeToken(baseCurrencyValue, msg.value); + } + + /** + External management functions (only the owner may update the sale) + */ + function update(Config calldata _config) external validUpdate(_config) onlyOwner { + config = _config; + // updates always reset the random value + randomValue = generatePseudorandomValue(config.merkleRoot); + emit Update(config); + } + + // Tell users where they can claim tokens + function registerDistributor(address _distributor) external onlyOwner { + require(_distributor != address(0), "Distributor == address(0)"); + distributor = _distributor; + emit RegisterDistributor(distributor); + } + + /** + Public management functions + */ + // Sweep an ERC20 token to the recipient (public function) + function sweepToken(IERC20Upgradeable token) external { + uint256 amount = token.balanceOf(address(this)); + token.safeTransfer(config.recipient, amount); + emit SweepToken(address(token), amount); + } + + // sweep native token to the recipient (public function) + function sweepNative() external { + uint256 amount = address(this).balance; + (bool success, ) = config.recipient.call{ value: amount }(""); + require(success, "Transfer failed."); + emit SweepNative(amount); + } +} diff --git a/zksync-ts/contracts/sale/v2.1/FlatPriceSaleFactory.sol b/zksync-ts/contracts/sale/v2.1/FlatPriceSaleFactory.sol new file mode 100644 index 00000000..24c9848d --- /dev/null +++ b/zksync-ts/contracts/sale/v2.1/FlatPriceSaleFactory.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.21; + +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "./FlatPriceSale.sol"; + +contract FlatPriceSaleFactory_v_2_1 { + address public immutable implementation; + string public constant VERSION = "2.0"; + + event NewSale( + address indexed implementation, + FlatPriceSale_v_2_1 indexed clone, + Config config, + string baseCurrency, + IOracleOrL2OracleWithSequencerCheck nativeOracle, + bool nativePaymentsEnabled + ); + + constructor(address _implementation) { + implementation = _implementation; + } + + function newSale( + address _owner, + Config calldata _config, + string calldata _baseCurrency, + bool _nativePaymentsEnabled, + IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, + IERC20Upgradeable[] calldata tokens, + IOracleOrL2OracleWithSequencerCheck[] calldata oracles, + uint8[] calldata decimals + ) external returns (FlatPriceSale_v_2_1 sale) { + sale = FlatPriceSale_v_2_1(Clones.clone(address(implementation))); + + emit NewSale( + implementation, + sale, + _config, + _baseCurrency, + _nativeTokenPriceOracle, + _nativePaymentsEnabled + ); + + sale.initialize( + _owner, + _config, + _baseCurrency, + _nativePaymentsEnabled, + _nativeTokenPriceOracle, + tokens, + oracles, + decimals + ); + } +} diff --git a/zksync-ts/contracts/sale/v2.1/Sale.sol b/zksync-ts/contracts/sale/v2.1/Sale.sol new file mode 100644 index 00000000..6679bd43 --- /dev/null +++ b/zksync-ts/contracts/sale/v2.1/Sale.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol"; + +// Upgradeable contracts are required to use clone() in SaleFactory +abstract contract Sale is ReentrancyGuardUpgradeable, OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + event Buy( + address indexed buyer, + address indexed token, + uint256 baseCurrencyValue, + uint256 tokenValue, + uint256 tokenFee + ); + + /** + Important: the constructor is only called once on the implementation contract (which is never initialized) + Clones using this implementation cannot use this constructor method. + Thus every clone must use the same fields stored in the constructor (feeBips, feeRecipient) + */ + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + // is this user permitted to access a sale? + function isValidMerkleProof( + bytes32 root, + address account, + bytes calldata data, + bytes32[] calldata proof + ) public pure returns (bool) { + // check if the account is in the merkle tree + bytes32 leaf = keccak256(abi.encodePacked(account, data)); + if (MerkleProofUpgradeable.verify(proof, root, leaf)) { + return true; + } + return false; + } + + function buyWithToken( + IERC20Upgradeable token, + uint256 quantity, + bytes calldata data, + bytes32[] calldata proof + ) external virtual {} + + function buyWithNative(bytes calldata data, bytes32[] calldata proof) external payable virtual {} + + function isOpen() public view virtual returns (bool) {} + + function isOver() public view virtual returns (bool) {} + + function buyerTotal(address user) external view virtual returns (uint256) {} + + function total() external view virtual returns (uint256) {} +} diff --git a/zksync-ts/contracts/sale/v2/FlatPriceSale.sol b/zksync-ts/contracts/sale/v2/FlatPriceSale.sol new file mode 100644 index 00000000..3b8a7d83 --- /dev/null +++ b/zksync-ts/contracts/sale/v2/FlatPriceSale.sol @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; +// pragma abicoder v2; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/PullPaymentUpgradeable.sol"; +import "./Sale.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +/** +Allow qualified users to participate in a sale according to sale rules. + +Management +- the address that deploys the sale is the sale owner +- owners may change some sale parameters (e.g. start and end times) +- sale proceeds are sent to the sale recipient + +Qualification +- public sale: anyone can participate +- private sale: only users who can prove membership in a merkle tree can participate + +Sale Rules +- timing: purchases can only be made + - after the sale opens + - after the per-account random queue time has elapsed + - before the sale ends +- purchase quantity: quantity is limited by + - per-address limit + - total sale limit +- payment method: participants can pay using either + - the native token on the network (e.g. ETH) + - a single ERC-20 token (e.g. USDC) +- number of purchases: there is no limit to the number of compliant purchases a user may make + +Token Distribution +- this contract does not distribute any purchased tokens + +Metrics +- purchase count: number of purchases made in this sale +- user count: number of unique addresses that participated in this sale +- total bought: value of purchases denominated in a base currency (e.g. USD) as an integer (to get the float value, divide by oracle decimals) +- bought per user: value of a user's purchases denominated in a base currency (e.g. USD) + +total bought and bought per user metrics are inclusive of any fee charged (if a fee is charged, the sale recipient will receive less than the total spend) +*/ + +// Sale can only be updated post-initialization by the contract owner! +struct Config { + // the address that will receive sale proceeds (tokens and native) minus any fees sent to the fee recipient + address payable recipient; + // the merkle root used for proving access + bytes32 merkleRoot; + // max that can be spent in the sale in the base currency + uint256 saleMaximum; + // max that can be spent per user in the base currency + uint256 userMaximum; + // minimum that can be bought in a specific purchase + uint256 purchaseMinimum; + // the time at which the sale starts (users will have an additional random delay if maxQueueTime is set) + uint256 startTime; + // the time at which the sale will end, regardless of tokens raised + uint256 endTime; + // what is the maximum length of time a user could wait in the queue after the sale starts? + uint256 maxQueueTime; + // a link to off-chain information about this sale + string URI; +} + +// Metrics are only updated by the buyWithToken() and buyWithNative() functions +struct Metrics { + // number of purchases + uint256 purchaseCount; + // number of buyers + uint256 buyerCount; + // amount bought denominated in a base currency + uint256 purchaseTotal; + // amount bought for each user denominated in a base currency + mapping(address => uint256) buyerTotal; +} + +struct PaymentTokenInfo { + IOracleOrL2OracleWithSequencerCheck oracle; + uint8 decimals; +} + +contract FlatPriceSale is Sale, PullPaymentUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + + event ImplementationConstructor(address payable indexed feeRecipient, uint256 feeBips); + event Update(Config config); + event Initialize( + Config config, + string baseCurrency, + IOracleOrL2OracleWithSequencerCheck nativeOracle, + bool nativePaymentsEnabled + ); + event SetPaymentTokenInfo(IERC20Upgradeable token, PaymentTokenInfo paymentTokenInfo); + event SweepToken(address indexed token, uint256 amount); + event SweepNative(uint256 amount); + event RegisterDistributor(address distributor); + + // All chainlink oracles used must have 8 decimals! + uint256 public constant BASE_CURRENCY_DECIMALS = 8; + // All supported chains must use 18 decimals (e.g. 1e18 wei / eth) + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + // flag for additional merkle root data + uint8 internal constant PER_USER_PURCHASE_LIMIT = 1; + uint8 internal constant PER_USER_END_TIME = 2; + + /** + Variables set by implementation contract constructor (immutable) + */ + + // a fee may be charged by the sale manager + uint256 immutable feeBips; + uint256 constant fractionDenominator = 10000; + + // the recipient of the fee + address payable immutable feeRecipient; + + // an optional address where buyers can receive distributed tokens + address distributor; + + /** + Variables set during initialization of clone contracts ("immutable" on each instance) + */ + + // the base currency being used, e.g. 'USD' + string public baseCurrency; + + string public constant VERSION = "2.2"; + + // / price, e.g. ETH/USD price + IOracleOrL2OracleWithSequencerCheck public nativeTokenPriceOracle; + + // whether native payments are enabled (set during intialization) + bool nativePaymentsEnabled; + + // / price oracles, eg USDC address => ETH/USDC price + mapping(IERC20Upgradeable => PaymentTokenInfo) public paymentTokens; + + // owner can update these + Config public config; + + // derived from payments + Metrics public metrics; + + // reasonably random value: xor of merkle root and blockhash for transaction setting merkle root + uint160 internal randomValue; + + // All clones will share the information in the implementation constructor + constructor(uint256 _feeBips, address payable _feeRecipient) { + if (_feeBips > 0) { + require(_feeRecipient != address(0), "feeRecipient == 0"); + } + feeRecipient = _feeRecipient; + feeBips = _feeBips; + + emit ImplementationConstructor(feeRecipient, feeBips); + } + + /** + Replacement for constructor for clones of the implementation contract + Important: anyone can call the initialize function! + */ + function initialize( + address _owner, + Config calldata _config, + string calldata _baseCurrency, + bool _nativePaymentsEnabled, + IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, + IERC20Upgradeable[] calldata tokens, + IOracleOrL2OracleWithSequencerCheck[] calldata oracles, + uint8[] calldata decimals + ) public initializer validUpdate(_config) { + // initialize the PullPayment escrow contract + __PullPayment_init(); + + // validate the new sale + require(tokens.length == oracles.length, "token and oracle lengths !="); + require(tokens.length == decimals.length, "token and decimals lengths !="); + require(address(_nativeTokenPriceOracle) != address(0), "native oracle == 0"); + require( + _nativeTokenPriceOracle.decimals() == BASE_CURRENCY_DECIMALS, + "native oracle decimals != 8" + ); + + // save the new sale + config = _config; + + // save payment config + baseCurrency = _baseCurrency; + nativeTokenPriceOracle = _nativeTokenPriceOracle; + nativePaymentsEnabled = _nativePaymentsEnabled; + emit Initialize(config, baseCurrency, nativeTokenPriceOracle, _nativePaymentsEnabled); + + for (uint256 i = 0; i < tokens.length; i++) { + // double check that tokens and oracles are real addresses + require(address(tokens[i]) != address(0), "payment token == 0"); + require(address(oracles[i]) != address(0), "token oracle == 0"); + // Double check that oracles use the expected 8 decimals + require(oracles[i].decimals() == BASE_CURRENCY_DECIMALS, "token oracle decimals != 8"); + // save the payment token info + paymentTokens[tokens[i]] = PaymentTokenInfo({ oracle: oracles[i], decimals: decimals[i] }); + + emit SetPaymentTokenInfo(tokens[i], paymentTokens[tokens[i]]); + } + + // Set the random value for the fair queue time + randomValue = generatePseudorandomValue(config.merkleRoot); + + // transfer ownership to the user initializing the sale + _transferOwnership(_owner); + } + + /** + Check that the user can currently participate in the sale based on the merkle root + + Merkle root options: + - bytes32(0): this is a public sale, any address can participate + - otherwise: this is a private sale, users must submit a merkle proof that their address is included in the merkle root + */ + modifier canAccessSale(bytes calldata data, bytes32[] calldata proof) { + // make sure the buyer is an EOA + // TODO: Review this check for meta-transactions + require((_msgSender() == tx.origin), "Must buy with an EOA"); + + // If the merkle root is non-zero this is a private sale and requires a valid proof + if (config.merkleRoot == bytes32(0)) { + // this is a public sale + // IMPORTANT: data is only validated if the merkle root is checked! Public sales do not check any merkle roots! + require(data.length == 0, "data not permitted on public sale"); + } else { + // this is a private sale + require( + this.isValidMerkleProof(config.merkleRoot, _msgSender(), data, proof) == true, + "bad merkle proof for sale" + ); + } + + // Require the sale to be open + require(block.timestamp > config.startTime, "sale has not started yet"); + require(block.timestamp < config.endTime, "sale has ended"); + require(metrics.purchaseTotal < config.saleMaximum, "sale buy limit reached"); + + // Reduce congestion by randomly assigning each user a delay time in a virtual queue based on comparing their address and a random value + // if config.maxQueueTime == 0 the delay is 0 + require( + block.timestamp - config.startTime > getFairQueueTime(_msgSender()), + "not your turn yet" + ); + _; + } + + /** + Check that the new sale is a valid update + - If the config already exists, it must not be over (cannot edit sale after it concludes) + - Sale start, end, and max queue times must be consistent and not too far in the future + */ + modifier validUpdate(Config calldata newConfig) { + // get the existing config + Config memory oldConfig = config; + + /** + - @notice - Block updates after sale is over + - @dev - Since validUpdate is called by initialize(), we can have a new + - sale here, identifiable by default randomValue of 0 + */ + if (randomValue != 0) { + // this is an existing sale: cannot update after it has ended + require(block.timestamp < oldConfig.endTime, "sale is over: cannot upate"); + if (block.timestamp > oldConfig.startTime) { + // the sale has already started, some things should not be edited + require( + oldConfig.saleMaximum == newConfig.saleMaximum, + "editing saleMaximum after sale start" + ); + } + } + + // the total sale limit must be at least as large as the per-user limit + + // all required values must be present and reasonable + // check if the caller accidentally entered a value in milliseconds instead of seconds + require(newConfig.startTime <= 4102444800, "start > 4102444800 (Jan 1 2100)"); + require(newConfig.endTime <= 4102444800, "end > 4102444800 (Jan 1 2100)"); + require(newConfig.maxQueueTime <= 604800, "max queue time > 604800 (1 week)"); + require(newConfig.recipient != address(0), "recipient == address(0)"); + + // sale, user, and purchase limits must be compatible + require(newConfig.saleMaximum > 0, "saleMaximum == 0"); + require(newConfig.userMaximum > 0, "userMaximum == 0"); + require(newConfig.userMaximum <= newConfig.saleMaximum, "userMaximum > saleMaximum"); + require(newConfig.purchaseMinimum <= newConfig.userMaximum, "purchaseMinimum > userMaximum"); + + // new sale times must be internally consistent + require( + newConfig.startTime + newConfig.maxQueueTime < newConfig.endTime, + "sale must be open for at least maxQueueTime" + ); + + _; + } + + modifier validPaymentToken(IERC20Upgradeable token) { + // check that this token is configured as a payment method + PaymentTokenInfo memory info = paymentTokens[token]; + require(address(info.oracle) != address(0), "invalid payment token"); + + _; + } + + modifier areNativePaymentsEnabled() { + require(nativePaymentsEnabled, "native payments disabled"); + + _; + } + + // Get info on a payment token + function getPaymentToken(IERC20Upgradeable token) + external + view + returns (PaymentTokenInfo memory) + { + return paymentTokens[token]; + } + + // Get a positive token price from a chainlink oracle + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle) public view returns (uint256) { + ( + uint80 roundID, + int256 _price, + /* uint256 startedAt */, + uint256 timeStamp, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(timeStamp > 0, "round not complete"); + require(answeredInRound >= roundID, "stale price"); + + return uint256(_price); + } + + /** + Generate a pseudorandom value + This is not a truly random value: + - miners can alter the block hash + - owners can repeatedly call setMerkleRoot() + - owners can choose when to submit the transaction + */ + function generatePseudorandomValue(bytes32 merkleRoot) public view returns (uint160) { + return uint160(uint256(blockhash(block.number - 1))) ^ uint160(uint256(merkleRoot)); + } + + /** + Get the delay in seconds that a specific buyer must wait after the sale begins in order to buy tokens in the sale + + Buyers cannot exploit the fair queue when: + - The sale is private (merkle root != bytes32(0)) + - Each eligible buyer gets exactly one address in the merkle root + + Although miners and sellers can minimize the delay for an arbitrary address, these are not significant threats: + - the economic opportunity to miners is zero or relatively small (only specific addresses can participate in private sales, and a better queue postion does not imply high returns) + - sellers can repeatedly set merkle roots to achieve a favorable queue time for any address, but sellers already control the tokens being sold! + */ + function getFairQueueTime(address buyer) public view returns (uint256) { + if (config.maxQueueTime == 0) { + // there is no delay: all addresses may participate immediately + return 0; + } + + // calculate a distance between the random value and the user's address using the XOR distance metric (c.f. Kademlia) + uint160 distance = uint160(buyer) ^ randomValue; + + // calculate a speed at which the queue is exhausted such that all users complete the queue by sale.maxQueueTime + uint160 distancePerSecond = type(uint160).max / uint160(config.maxQueueTime); + // return the delay (seconds) + return distance / distancePerSecond; + } + + /** + Convert a token quantity (e.g. USDC or ETH) to a base currency (e.g. USD) with the same number of decimals as the price oracle (e.g. 8) + + Example: given 2 NCT tokens, each worth $1.23, tokensToBaseCurrency should return 246000000 ($2.46) + + Function arguments + - tokenQuantity: 2000000000000000000 + - tokenDecimals: 18 + + NCT/USD chainlink oracle (important! the oracle must be / not /, e.g. ETH/USD, ~$2000 not USD/ETH, ~0.0005) + - baseCurrencyPerToken: 123000000 + - baseCurrencyDecimals: 8 + + Calculation: 2000000000000000000 * 123000000 / 1000000000000000000 + + Returns: 246000000 + */ + // function tokensToBaseCurrency(SafeERC20Upgradeable token, uint256 quantity) public view validPaymentToken(token) returns (uint256) { + // PaymentTokenInfo info = paymentTokens[token]; + // return quantity * getOraclePrice(info.oracle) / (10 ** info.decimals); + // } + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle)) / (10**tokenDecimals); + } + + function total() external view override returns (uint256) { + return metrics.purchaseTotal; + } + + function isOver() public view override returns (bool) { + return config.endTime <= block.timestamp || metrics.purchaseTotal >= config.saleMaximum; + } + + function isOpen() public view override returns (bool) { + return + config.startTime < block.timestamp && + config.endTime > block.timestamp && + metrics.purchaseTotal < config.saleMaximum; + } + + // return the amount bought by this user in base currency + function buyerTotal(address user) external view override returns (uint256) { + return metrics.buyerTotal[user]; + } + + /** + Records a purchase + Follow the Checks -> Effects -> Interactions pattern + * Checks: CALLER MUST ENSURE BUYER IS PERMITTED TO PARTICIPATE IN THIS SALE: THIS METHOD DOES NOT CHECK WHETHER THE BUYER SHOULD BE ABLE TO ACCESS THE SALE! + * Effects: record the payment + * Interactions: none! + */ + function _execute(uint256 baseCurrencyQuantity, bytes calldata data) internal { + // Checks + uint256 userLimit = config.userMaximum; + + if (data.length > 0) { + require(uint8(bytes1(data[0:1])) == PER_USER_PURCHASE_LIMIT, "unknown data"); + require(data.length == 33, "data length != 33 bytes"); + userLimit = uint256(bytes32(data[1:33])); + } + + require( + baseCurrencyQuantity + metrics.buyerTotal[_msgSender()] <= userLimit, + "purchase exceeds your limit" + ); + + require( + baseCurrencyQuantity + metrics.purchaseTotal <= config.saleMaximum, + "purchase exceeds sale limit" + ); + + require(baseCurrencyQuantity >= config.purchaseMinimum, "purchase under minimum"); + + // Effects + metrics.purchaseCount += 1; + if (metrics.buyerTotal[_msgSender()] == 0) { + // if no prior purchases, this is a new buyer + metrics.buyerCount += 1; + } + metrics.purchaseTotal += baseCurrencyQuantity; + metrics.buyerTotal[_msgSender()] += baseCurrencyQuantity; + } + + /** + Settle payment made with payment token + Important: this function has no checks! Only call if the purchase is valid! + */ + function _settlePaymentToken( + uint256 baseCurrencyValue, + IERC20Upgradeable token, + uint256 quantity + ) internal { + uint256 fee = 0; + if (feeBips > 0) { + fee = (quantity * feeBips) / fractionDenominator; + token.safeTransferFrom(_msgSender(), feeRecipient, fee); + } + token.safeTransferFrom(_msgSender(), address(this), quantity - fee); + emit Buy(_msgSender(), address(token), baseCurrencyValue, quantity, fee); + } + + /** + Settle payment made with native token + Important: this function has no checks! Only call if the purchase is valid! + */ + function _settleNativeToken(uint256 baseCurrencyValue, uint256 nativeTokenQuantity) internal { + uint256 nativeFee = 0; + if (feeBips > 0) { + nativeFee = (nativeTokenQuantity * feeBips) / fractionDenominator; + _asyncTransfer(feeRecipient, nativeFee); + } + _asyncTransfer(config.recipient, nativeTokenQuantity - nativeFee); + // This contract will hold the native token until claimed by the owner + emit Buy(_msgSender(), address(0), baseCurrencyValue, nativeTokenQuantity, nativeFee); + } + + /** + Pay with the payment token (e.g. USDC) + */ + function buyWithToken( + IERC20Upgradeable token, + uint256 quantity, + bytes calldata data, + bytes32[] calldata proof + ) external override canAccessSale(data, proof) validPaymentToken(token) nonReentrant { + // convert to base currency from native tokens + PaymentTokenInfo memory tokenInfo = paymentTokens[token]; + uint256 baseCurrencyValue = tokensToBaseCurrency( + quantity, + tokenInfo.decimals, + tokenInfo.oracle + ); + // Checks and Effects + _execute(baseCurrencyValue, data); + // Interactions + _settlePaymentToken(baseCurrencyValue, token, quantity); + } + + /** + Pay with the native token (e.g. ETH) + */ + function buyWithNative(bytes calldata data, bytes32[] calldata proof) + external + payable + override + canAccessSale(data, proof) + areNativePaymentsEnabled + nonReentrant + { + // convert to base currency from native tokens + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle + ); + // Checks and Effects + _execute(baseCurrencyValue, data); + // Interactions + _settleNativeToken(baseCurrencyValue, msg.value); + } + + /** + External management functions (only the owner may update the sale) + */ + function update(Config calldata _config) external validUpdate(_config) onlyOwner { + config = _config; + // updates always reset the random value + randomValue = generatePseudorandomValue(config.merkleRoot); + emit Update(config); + } + + // Tell users where they can claim tokens + function registerDistributor(address _distributor) external onlyOwner { + require(_distributor != address(0), "Distributor == address(0)"); + distributor = _distributor; + emit RegisterDistributor(distributor); + } + + /** + Public management functions + */ + // Sweep an ERC20 token to the recipient (public function) + function sweepToken(IERC20Upgradeable token) external { + uint256 amount = token.balanceOf(address(this)); + token.safeTransfer(config.recipient, amount); + emit SweepToken(address(token), amount); + } + + // sweep native token to the recipient (public function) + function sweepNative() external { + uint256 amount = address(this).balance; + (bool success, ) = config.recipient.call{ value: amount }(""); + require(success, "Transfer failed."); + emit SweepNative(amount); + } +} diff --git a/zksync-ts/contracts/sale/v2/FlatPriceSaleFactory.sol b/zksync-ts/contracts/sale/v2/FlatPriceSaleFactory.sol new file mode 100644 index 00000000..0c0cc72d --- /dev/null +++ b/zksync-ts/contracts/sale/v2/FlatPriceSaleFactory.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.21; + +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "./FlatPriceSale.sol"; + +contract FlatPriceSaleFactory { + address public immutable implementation; + string public constant VERSION = "2.0"; + + event NewSale( + address indexed implementation, + FlatPriceSale indexed clone, + Config config, + string baseCurrency, + IOracleOrL2OracleWithSequencerCheck nativeOracle, + bool nativePaymentsEnabled + ); + + constructor(address _implementation) { + implementation = _implementation; + } + + function newSale( + address _owner, + Config calldata _config, + string calldata _baseCurrency, + bool _nativePaymentsEnabled, + IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, + IERC20Upgradeable[] calldata tokens, + IOracleOrL2OracleWithSequencerCheck[] calldata oracles, + uint8[] calldata decimals + ) external returns (FlatPriceSale sale) { + sale = FlatPriceSale(Clones.clone(address(implementation))); + + emit NewSale( + implementation, + sale, + _config, + _baseCurrency, + _nativeTokenPriceOracle, + _nativePaymentsEnabled + ); + + sale.initialize( + _owner, + _config, + _baseCurrency, + _nativePaymentsEnabled, + _nativeTokenPriceOracle, + tokens, + oracles, + decimals + ); + } +} diff --git a/zksync-ts/contracts/sale/v2/Sale.sol b/zksync-ts/contracts/sale/v2/Sale.sol new file mode 100644 index 00000000..6679bd43 --- /dev/null +++ b/zksync-ts/contracts/sale/v2/Sale.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol"; + +// Upgradeable contracts are required to use clone() in SaleFactory +abstract contract Sale is ReentrancyGuardUpgradeable, OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + event Buy( + address indexed buyer, + address indexed token, + uint256 baseCurrencyValue, + uint256 tokenValue, + uint256 tokenFee + ); + + /** + Important: the constructor is only called once on the implementation contract (which is never initialized) + Clones using this implementation cannot use this constructor method. + Thus every clone must use the same fields stored in the constructor (feeBips, feeRecipient) + */ + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + // is this user permitted to access a sale? + function isValidMerkleProof( + bytes32 root, + address account, + bytes calldata data, + bytes32[] calldata proof + ) public pure returns (bool) { + // check if the account is in the merkle tree + bytes32 leaf = keccak256(abi.encodePacked(account, data)); + if (MerkleProofUpgradeable.verify(proof, root, leaf)) { + return true; + } + return false; + } + + function buyWithToken( + IERC20Upgradeable token, + uint256 quantity, + bytes calldata data, + bytes32[] calldata proof + ) external virtual {} + + function buyWithNative(bytes calldata data, bytes32[] calldata proof) external payable virtual {} + + function isOpen() public view virtual returns (bool) {} + + function isOver() public view virtual returns (bool) {} + + function buyerTotal(address user) external view virtual returns (uint256) {} + + function total() external view virtual returns (uint256) {} +} diff --git a/zksync-ts/contracts/sale/v3/FlatPriceSale.sol b/zksync-ts/contracts/sale/v3/FlatPriceSale.sol new file mode 100644 index 00000000..41ff1049 --- /dev/null +++ b/zksync-ts/contracts/sale/v3/FlatPriceSale.sol @@ -0,0 +1,630 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +// pragma abicoder v2; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/PullPaymentUpgradeable.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import "./Sale.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import "../../config/INetworkConfig.sol"; +import {IFeeLevelJudge} from "../../claim/IFeeLevelJudge.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +/** +Allow qualified users to participate in a sale according to sale rules. + +Management +- the address that deploys the sale is the sale owner +- owners may change some sale parameters (e.g. start and end times) +- sale proceeds are sent to the sale recipient + +Qualification +- public sale: anyone can participate +- private sale: only users who can prove membership in a merkle tree can participate + +Sale Rules +- timing: purchases can only be made + - after the sale opens + - after the per-account random queue time has elapsed + - before the sale ends +- purchase quantity: quantity is limited by + - per-address limit + - total sale limit +- payment method: participants can pay using either + - the native token on the network (e.g. ETH) + - a single ERC-20 token (e.g. USDC) +- number of purchases: there is no limit to the number of compliant purchases a user may make + +Token Distribution +- this contract does not distribute any purchased tokens + +Metrics +- purchase count: number of purchases made in this sale +- user count: number of unique addresses that participated in this sale +- total bought: value of purchases denominated in a base currency (e.g. USD) as an integer (to get the float value, divide by oracle decimals) +- bought per user: value of a user's purchases denominated in a base currency (e.g. USD) + +total bought and bought per user metrics are inclusive of any fee charged (if a fee is charged, the sale recipient will receive less than the total spend) +*/ + +// Sale can only be updated post-initialization by the contract owner! +struct Config { + // the address that will receive sale proceeds (tokens and native) minus any fees sent to the fee recipient + address payable recipient; + // the merkle root used for proving access + bytes32 merkleRoot; + // max that can be spent in the sale in the base currency + uint256 saleMaximum; + // max that can be spent per user in the base currency + uint256 userMaximum; + // minimum that can be bought in a specific purchase + uint256 purchaseMinimum; + // the time at which the sale starts (users will have an additional random delay if maxQueueTime is set) + uint256 startTime; + // the time at which the sale will end, regardless of tokens raised + uint256 endTime; + // what is the maximum length of time a user could wait in the queue after the sale starts? + uint256 maxQueueTime; + // a link to off-chain information about this sale + string URI; +} + +// Metrics are only updated by the buyWithToken() and buyWithNative() functions +struct Metrics { + // number of purchases + uint256 purchaseCount; + // number of buyers + uint256 buyerCount; + // amount bought denominated in a base currency + uint256 purchaseTotal; + // amount bought for each user denominated in a base currency + mapping(address => uint256) buyerTotal; +} + +struct PaymentTokenInfo { + IOracleOrL2OracleWithSequencerCheck oracle; + uint256 heartbeat; + uint8 decimals; +} + +contract FlatPriceSale_v_3 is Sale, PullPaymentUpgradeable { + using Address for address payable; + using SafeERC20Upgradeable for IERC20Upgradeable; + + event ImplementationConstructor(INetworkConfig networkConfig); + event Update(Config config); + event Initialize( + Config config, + string baseCurrency, + IOracleOrL2OracleWithSequencerCheck nativeOracle, + bool nativePaymentsEnabled + ); + event SetPaymentTokenInfo(IERC20Upgradeable token, PaymentTokenInfo paymentTokenInfo); + event SweepToken(address indexed token, uint256 amount); + event SweepNative(uint256 amount); + event RegisterDistributor(address distributor); + + // All supported chains must use 18 decimals (e.g. 1e18 wei / eth) + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + // flag for additional merkle root data + uint8 internal constant PER_USER_PURCHASE_LIMIT = 1; + uint8 internal constant PER_USER_END_TIME = 2; + + /** + Variables set by implementation contract constructor (immutable) + */ + + // denominator used to determine size of fee bips + uint256 constant fractionDenominator = 10000; + + // an optional address where buyers can receive distributed tokens + address distributor; + + /** + Variables set during initialization of clone contracts ("immutable" on each instance) + */ + + // the base currency being used, e.g. 'USD' + string public baseCurrency; + + string public constant VERSION = "3.0"; + + // / price, e.g. ETH/USD price + IOracleOrL2OracleWithSequencerCheck public nativeTokenPriceOracle; + + // heartbeat value for oracle + uint256 public nativeTokenPriceOracleHeartbeat; + + // whether native payments are enabled (set during intialization) + bool nativePaymentsEnabled; + + // / price oracles, eg USDC address => ETH/USDC price + mapping(IERC20Upgradeable => PaymentTokenInfo) public paymentTokens; + + // owner can update these + Config public config; + + // derived from payments + Metrics public metrics; + + // reasonably random value: xor of merkle root and blockhash for transaction setting merkle root + uint160 internal randomValue; + + INetworkConfig public immutable networkConfig; + + // All clones will share the information in the implementation constructor + constructor(address _networkConfig) { + networkConfig = INetworkConfig(_networkConfig); + + emit ImplementationConstructor(networkConfig); + } + + /** + Replacement for constructor for clones of the implementation contract + Important: anyone can call the initialize function! + */ + function initialize( + address _owner, + Config calldata _config, + string calldata _baseCurrency, + bool _nativePaymentsEnabled, + IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, + uint256 _nativeTokenPriceOracleHeartbeat, + IERC20Upgradeable[] calldata tokens, + IOracleOrL2OracleWithSequencerCheck[] calldata oracles, + uint256[] calldata oracleHeartbeats, + uint8[] calldata decimals + ) public initializer validUpdate(_config) { + // initialize the PullPayment escrow contract + __PullPayment_init(); + + __ReentrancyGuard_init(); + + // validate the new sale + require(tokens.length == oracles.length, "token and oracle lengths !="); + require(tokens.length == decimals.length, "token and decimals lengths !="); + require(address(_nativeTokenPriceOracle) != address(0), "native oracle == 0"); + + // save the new sale + config = _config; + + // save payment config + baseCurrency = _baseCurrency; + nativeTokenPriceOracle = _nativeTokenPriceOracle; + nativeTokenPriceOracleHeartbeat = _nativeTokenPriceOracleHeartbeat; + nativePaymentsEnabled = _nativePaymentsEnabled; + emit Initialize(config, baseCurrency, nativeTokenPriceOracle, _nativePaymentsEnabled); + + for (uint256 i = 0; i < tokens.length; i++) { + // double check that tokens and oracles are real addresses + require(address(tokens[i]) != address(0), "payment token == 0"); + require(address(oracles[i]) != address(0), "token oracle == 0"); + // save the payment token info + paymentTokens[tokens[i]] = PaymentTokenInfo({ oracle: oracles[i], heartbeat: oracleHeartbeats[i], decimals: decimals[i] }); + + emit SetPaymentTokenInfo(tokens[i], paymentTokens[tokens[i]]); + } + + // Set the random value for the fair queue time + randomValue = generatePseudorandomValue(config.merkleRoot); + + // transfer ownership to the user initializing the sale + _transferOwnership(_owner); + } + + /** + Check that the user can currently participate in the sale based on the merkle root + + Merkle root options: + - bytes32(0): this is a public sale, any address can participate + - otherwise: this is a private sale, users must submit a merkle proof that their address is included in the merkle root + */ + modifier canAccessSale(bytes calldata data, bytes32[] calldata proof) { + // make sure the buyer is an EOA + require((_msgSender() == tx.origin), "Must buy with an EOA"); + + // If the merkle root is non-zero this is a private sale and requires a valid proof + if (config.merkleRoot == bytes32(0)) { + // this is a public sale + // IMPORTANT: data is only validated if the merkle root is checked! Public sales do not check any merkle roots! + require(data.length == 0, "data not permitted on public sale"); + } else { + // this is a private sale + require( + this.isValidMerkleProof(config.merkleRoot, _msgSender(), data, proof) == true, + "bad merkle proof for sale" + ); + } + + // Require the sale to be open + require(block.timestamp > config.startTime, "sale has not started yet"); + require(block.timestamp < config.endTime, "sale has ended"); + require(metrics.purchaseTotal < config.saleMaximum, "sale buy limit reached"); + + // Reduce congestion by randomly assigning each user a delay time in a virtual queue based on comparing their address and a random value + // if config.maxQueueTime == 0 the delay is 0 + require( + block.timestamp - config.startTime > getFairQueueTime(_msgSender()), + "not your turn yet" + ); + _; + } + + /** + Check that the new sale is a valid update + - If the config already exists, it must not be over (cannot edit sale after it concludes) + - Sale start, end, and max queue times must be consistent and not too far in the future + */ + modifier validUpdate(Config calldata newConfig) { + // get the existing config + Config memory oldConfig = config; + + /** + - @notice - Block updates after sale is over + - @dev - Since validUpdate is called by initialize(), we can have a new + - sale here, identifiable by default randomValue of 0 + */ + if (randomValue != 0) { + // this is an existing sale: cannot update after it has ended + require(block.timestamp < oldConfig.endTime, "sale is over: cannot upate"); + if (block.timestamp > oldConfig.startTime) { + // the sale has already started, some things should not be edited + require( + oldConfig.saleMaximum == newConfig.saleMaximum, + "editing saleMaximum after sale start" + ); + } + } + + // the total sale limit must be at least as large as the per-user limit + + // all required values must be present and reasonable + // check if the caller accidentally entered a value in milliseconds instead of seconds + require(newConfig.startTime <= 4102444800, "start > 4102444800 (Jan 1 2100)"); + require(newConfig.endTime <= 4102444800, "end > 4102444800 (Jan 1 2100)"); + require(newConfig.maxQueueTime <= 604800, "max queue time > 604800 (1 week)"); + require(newConfig.recipient != address(0), "recipient == address(0)"); + + // sale, user, and purchase limits must be compatible + require(newConfig.saleMaximum > 0, "saleMaximum == 0"); + require(newConfig.userMaximum > 0, "userMaximum == 0"); + require(newConfig.userMaximum <= newConfig.saleMaximum, "userMaximum > saleMaximum"); + require(newConfig.purchaseMinimum <= newConfig.userMaximum, "purchaseMinimum > userMaximum"); + + // new sale times must be internally consistent + require( + newConfig.startTime + newConfig.maxQueueTime < newConfig.endTime, + "sale must be open for at least maxQueueTime" + ); + + _; + } + + modifier validPaymentToken(IERC20Upgradeable token) { + // check that this token is configured as a payment method + PaymentTokenInfo memory info = paymentTokens[token]; + require(address(info.oracle) != address(0), "invalid payment token"); + + _; + } + + modifier areNativePaymentsEnabled() { + require(nativePaymentsEnabled, "native payments disabled"); + + _; + } + + // Get info on a payment token + function getPaymentToken(IERC20Upgradeable token) + external + view + returns (PaymentTokenInfo memory) + { + return paymentTokens[token]; + } + + // TODO: reduce duplication between other contracts + // Get a positive token price from a chainlink oracle + function getOraclePrice(IOracleOrL2OracleWithSequencerCheck oracle, uint256 heartbeat) public view returns (uint256) { + ( + , + int256 _price, + , + uint256 updatedAt, + uint80 answeredInRound + ) = oracle.latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require(updatedAt > block.timestamp - heartbeat, "stale price due to heartbeat"); + + return uint256(_price); + } + + /** + Generate a pseudorandom value + This is not a truly random value: + - miners can alter the block hash + - owners can repeatedly call setMerkleRoot() + - owners can choose when to submit the transaction + */ + function generatePseudorandomValue(bytes32 merkleRoot) public view returns (uint160) { + return uint160(uint256(blockhash(block.number - 1))) ^ uint160(uint256(merkleRoot)); + } + + /** + Get the delay in seconds that a specific buyer must wait after the sale begins in order to buy tokens in the sale + + Buyers cannot exploit the fair queue when: + - The sale is private (merkle root != bytes32(0)) + - Each eligible buyer gets exactly one address in the merkle root + + Although miners and sellers can minimize the delay for an arbitrary address, these are not significant threats: + - the economic opportunity to miners is zero or relatively small (only specific addresses can participate in private sales, and a better queue postion does not imply high returns) + - sellers can repeatedly set merkle roots to achieve a favorable queue time for any address, but sellers already control the tokens being sold! + */ + function getFairQueueTime(address buyer) public view returns (uint256) { + if (config.maxQueueTime == 0) { + // there is no delay: all addresses may participate immediately + return 0; + } + + // calculate a distance between the random value and the user's address using the XOR distance metric (c.f. Kademlia) + uint160 distance = uint160(buyer) ^ randomValue; + + // calculate a speed at which the queue is exhausted such that all users complete the queue by sale.maxQueueTime + uint160 distancePerSecond = type(uint160).max / uint160(config.maxQueueTime); + // return the delay (seconds) + return distance / distancePerSecond; + } + + /** + Convert a token quantity (e.g. USDC or ETH) to a base currency (e.g. USD) with the same number of decimals as the price oracle (e.g. 8) + + Example: given 2 NCT tokens, each worth $1.23, tokensToBaseCurrency should return 246000000 ($2.46) + + Function arguments + - tokenQuantity: 2000000000000000000 + - tokenDecimals: 18 + + NCT/USD chainlink oracle (important! the oracle must be / not /, e.g. ETH/USD, ~$2000 not USD/ETH, ~0.0005) + - baseCurrencyPerToken: 123000000 + - baseCurrencyDecimals: 8 + + Calculation: 2000000000000000000 * 123000000 / 1000000000000000000 + + Returns: 246000000 + */ + // function tokensToBaseCurrency(SafeERC20Upgradeable token, uint256 quantity) public view validPaymentToken(token) returns (uint256) { + // PaymentTokenInfo info = paymentTokens[token]; + // return quantity * getOraclePrice(info.oracle) / (10 ** info.decimals); + // } + // TODO: reduce duplication between other contracts + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return (tokenQuantity * getOraclePrice(oracle, heartbeat)) / (10**tokenDecimals); + } + + function total() external view override returns (uint256) { + return metrics.purchaseTotal; + } + + function isOver() public view override returns (bool) { + return config.endTime <= block.timestamp || metrics.purchaseTotal >= config.saleMaximum; + } + + function isOpen() public view override returns (bool) { + return + config.startTime < block.timestamp && + config.endTime > block.timestamp && + metrics.purchaseTotal < config.saleMaximum; + } + + // return the amount bought by this user in base currency + function buyerTotal(address user) external view override returns (uint256) { + return metrics.buyerTotal[user]; + } + + /** + Records a purchase + Follow the Checks -> Effects -> Interactions pattern + * Checks: CALLER MUST ENSURE BUYER IS PERMITTED TO PARTICIPATE IN THIS SALE: THIS METHOD DOES NOT CHECK WHETHER THE BUYER SHOULD BE ABLE TO ACCESS THE SALE! + * Effects: record the payment + * Interactions: none! + */ + function _execute(uint256 baseCurrencyQuantity, bytes calldata data) internal { + // Checks + uint256 userLimit = config.userMaximum; + + if (data.length > 0) { + require(uint8(bytes1(data[0:1])) == PER_USER_PURCHASE_LIMIT, "unknown data"); + require(data.length == 33, "data length != 33 bytes"); + userLimit = uint256(bytes32(data[1:33])); + } + + require( + baseCurrencyQuantity + metrics.buyerTotal[_msgSender()] <= userLimit, + "purchase exceeds your limit" + ); + + require( + baseCurrencyQuantity + metrics.purchaseTotal <= config.saleMaximum, + "purchase exceeds sale limit" + ); + + require(baseCurrencyQuantity >= config.purchaseMinimum, "purchase under minimum"); + + // Effects + metrics.purchaseCount += 1; + if (metrics.buyerTotal[_msgSender()] == 0) { + // if no prior purchases, this is a new buyer + metrics.buyerCount += 1; + } + metrics.purchaseTotal += baseCurrencyQuantity; + metrics.buyerTotal[_msgSender()] += baseCurrencyQuantity; + } + + /** + Settle payment made with payment token + Important: this function has no checks! Only call if the purchase is valid! + */ + function _settlePaymentToken( + uint256 baseCurrencyValue, + IERC20Upgradeable token, + uint256 quantity, + uint256 feeLevel, + uint256 platformFlatRateFeeAmount + ) internal { + uint256 fee = (quantity * feeLevel) / fractionDenominator; + token.safeTransferFrom(_msgSender(), networkConfig.getFeeRecipient(), fee); + token.safeTransferFrom(_msgSender(), address(this), quantity - fee); + emit Buy(_msgSender(), address(token), baseCurrencyValue, quantity, fee, platformFlatRateFeeAmount); + } + + /** + Settle payment made with native token + Important: this function has no checks! Only call if the purchase is valid! + */ + function _settleNativeToken(uint256 baseCurrencyValue, uint256 nativeTokenQuantity, uint256 feeLevel, uint256 platformFlatRateFeeAmount) internal { + uint256 nativeFee = (nativeTokenQuantity * feeLevel) / fractionDenominator; + _asyncTransfer(networkConfig.getFeeRecipient(), nativeFee); + _asyncTransfer(config.recipient, nativeTokenQuantity - nativeFee - platformFlatRateFeeAmount); + + // This contract will hold the native token until claimed by the owner + emit Buy(_msgSender(), address(0), baseCurrencyValue, nativeTokenQuantity, nativeFee, platformFlatRateFeeAmount); + } + + /** + Pay with the payment token (e.g. USDC) + */ + function buyWithToken( + IERC20Upgradeable token, + uint256 quantity, + bytes calldata data, + bytes32[] calldata proof, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) external payable override canAccessSale(data, proof) validPaymentToken(token) nonReentrant { + uint256 nativeBaseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(nativeBaseCurrencyValue >= platformFlatRateFeeAmount, "fee payment below minimum"); + + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + + // convert to base currency from payment tokens + PaymentTokenInfo memory tokenInfo = paymentTokens[token]; + uint256 baseCurrencyValue = tokensToBaseCurrency( + quantity, + tokenInfo.decimals, + tokenInfo.oracle, + tokenInfo.heartbeat + ); + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge(networkConfig.getStakingAddress()); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + + // Checks and Effects + _execute(baseCurrencyValue + platformFlatRateFeeAmount, data); + // Interactions + _settlePaymentToken(baseCurrencyValue + platformFlatRateFeeAmount, token, quantity, feeLevel, platformFlatRateFeeAmount); + } + + /** + Pay with the native token (e.g. ETH) + */ + function buyWithNative( + bytes calldata data, + bytes32[] calldata proof, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) external + payable + override + canAccessSale(data, proof) + areNativePaymentsEnabled + nonReentrant + { + // convert to base currency from native tokens + // converts msg.value (which is how much ETH was sent by user) to USD + // Example: On September 1st, 2024 + // 0.005 * 2450**10e8 => ~$10 = how much in USD represents + // msg.value + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require(baseCurrencyValue >= platformFlatRateFeeAmount, "fee payment below minimum"); + + // Example: On September 1st, 2024 + // 1/2450 * 10**18 = how much ETH (in wei) we need to represent + // 1 USD + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * (10 ** NATIVE_TOKEN_DECIMALS)) / getOraclePrice(nativeTokenPriceOracle, nativeTokenPriceOracleHeartbeat)); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge(networkConfig.getStakingAddress()); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + // Checks and Effects + _execute(baseCurrencyValue, data); + // Interactions + _settleNativeToken(baseCurrencyValue, msg.value, feeLevel, feeAmountInWei); + } + + /** + External management functions (only the owner may update the sale) + */ + function update(Config calldata _config) external validUpdate(_config) onlyOwner { + config = _config; + // updates always reset the random value + randomValue = generatePseudorandomValue(config.merkleRoot); + emit Update(config); + } + + // Tell users where they can claim tokens + function registerDistributor(address _distributor) external onlyOwner { + require(_distributor != address(0), "Distributor == address(0)"); + distributor = _distributor; + emit RegisterDistributor(distributor); + } + + /** + Public management functions + */ + // Sweep an ERC20 token to the recipient (public function) + function sweepToken(IERC20Upgradeable token) external { + uint256 amount = token.balanceOf(address(this)); + token.safeTransfer(config.recipient, amount); + emit SweepToken(address(token), amount); + } + + // sweep native token to the recipient (public function) + function sweepNative() external { + uint256 amount = address(this).balance; + (bool success, ) = config.recipient.call{ value: amount }(""); + require(success, "Transfer failed."); + emit SweepNative(amount); + } +} diff --git a/zksync-ts/contracts/sale/v3/FlatPriceSaleFactory.sol b/zksync-ts/contracts/sale/v3/FlatPriceSaleFactory.sol new file mode 100644 index 00000000..9176a1c9 --- /dev/null +++ b/zksync-ts/contracts/sale/v3/FlatPriceSaleFactory.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.21; + +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "./FlatPriceSale.sol"; + +contract FlatPriceSaleFactory_v_3 is Ownable { + address public implementation; + string public constant VERSION = "3.0"; + + event NewSale( + address indexed implementation, + FlatPriceSale_v_3 indexed clone, + Config config, + string baseCurrency, + IOracleOrL2OracleWithSequencerCheck nativeOracle, + uint256 nativeOracleHeartbeat, + bool nativePaymentsEnabled + ); + + constructor(address _implementation) { + implementation = _implementation; + } + + function upgradeFutureSales(address _implementation) external onlyOwner() { + implementation = _implementation; + } + + function newSale( + address _owner, + Config calldata _config, + string calldata _baseCurrency, + bool _nativePaymentsEnabled, + IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, + uint256 _nativeTokenPriceOracleHeartbeat, + IERC20Upgradeable[] calldata tokens, + IOracleOrL2OracleWithSequencerCheck[] calldata oracles, + uint256[] calldata oracleHeartbeats, + uint8[] calldata decimals + ) external returns (FlatPriceSale_v_3 sale) { + sale = FlatPriceSale_v_3(Clones.clone(address(implementation))); + + emit NewSale( + implementation, + sale, + _config, + _baseCurrency, + _nativeTokenPriceOracle, + _nativeTokenPriceOracleHeartbeat, + _nativePaymentsEnabled + ); + + sale.initialize( + _owner, + _config, + _baseCurrency, + _nativePaymentsEnabled, + _nativeTokenPriceOracle, + _nativeTokenPriceOracleHeartbeat, + tokens, + oracles, + oracleHeartbeats, + decimals + ); + } +} diff --git a/zksync-ts/contracts/sale/v3/Sale.sol b/zksync-ts/contracts/sale/v3/Sale.sol new file mode 100644 index 00000000..67c7b55f --- /dev/null +++ b/zksync-ts/contracts/sale/v3/Sale.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol"; + +// Upgradeable contracts are required to use clone() in SaleFactory +abstract contract Sale is ReentrancyGuardUpgradeable, OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + event Buy( + address indexed buyer, + address indexed token, + uint256 baseCurrencyValue, + uint256 tokenValue, + uint256 protocolTokenFee, + uint256 platformTokenFee + ); + + /** + Important: the constructor is only called once on the implementation contract (which is never initialized) + Clones using this implementation cannot use this constructor method. + Thus every clone must use the same fields stored in the constructor (feeBips, feeRecipient) + */ + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + // is this user permitted to access a sale? + function isValidMerkleProof( + bytes32 root, + address account, + bytes calldata data, + bytes32[] calldata proof + ) public pure returns (bool) { + // check if the account is in the merkle tree + bytes32 leaf = keccak256(abi.encodePacked(account, data)); + if (MerkleProofUpgradeable.verify(proof, root, leaf)) { + return true; + } + return false; + } + + function buyWithToken( + IERC20Upgradeable token, + uint256 quantity, + bytes calldata data, + bytes32[] calldata proof, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) external payable virtual {} + + function buyWithNative( + bytes calldata data, + bytes32[] calldata proof, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) external payable virtual {} + + function isOpen() public view virtual returns (bool) {} + + function isOver() public view virtual returns (bool) {} + + function buyerTotal(address user) external view virtual returns (uint256) {} + + function total() external view virtual returns (uint256) {} +} diff --git a/zksync-ts/contracts/sale/v4/FlatPriceSale.sol b/zksync-ts/contracts/sale/v4/FlatPriceSale.sol new file mode 100644 index 00000000..a4e7bdd8 --- /dev/null +++ b/zksync-ts/contracts/sale/v4/FlatPriceSale.sol @@ -0,0 +1,737 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +// pragma abicoder v2; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/PullPaymentUpgradeable.sol"; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import "./Sale.sol"; +import "../../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; +import "../../config/INetworkConfig.sol"; +import "../../utilities/AccessVerifier.sol"; +import { IFeeLevelJudge } from "../../claim/IFeeLevelJudge.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +/** +Allow qualified users to participate in a sale according to sale rules. + +Management +- the address that deploys the sale is the sale owner +- owners may change some sale parameters (e.g. start and end times) +- sale proceeds are sent to the sale recipient + +Qualification +- (deprecated) public sale: anyone can participate +- private sale: only users who have signatures issued by the access authority + +Sale Rules +- timing: purchases can only be made + - after the sale opens + - after the per-account random queue time has elapsed + - before the sale ends +- purchase quantity: quantity is limited by + - per-address limit + - total sale limit +- payment method: participants can pay using either + - the native token on the network (e.g. ETH) + - a single ERC-20 token (e.g. USDC) +- number of purchases: there is no limit to the number of compliant purchases a user may make + +Token Distribution +- this contract does not distribute any purchased tokens + +Metrics +- purchase count: number of purchases made in this sale +- user count: number of unique addresses that participated in this sale +- total bought: value of purchases denominated in a base currency (e.g. USD) as an integer (to get the float value, divide by oracle decimals) +- bought per user: value of a user's purchases denominated in a base currency (e.g. USD) + +total bought and bought per user metrics are inclusive of any fee charged (if a fee is charged, the sale recipient will receive less than the total spend) +*/ + +// Sale can only be updated post-initialization by the contract owner! +struct Config { + // the address that will receive sale proceeds (tokens and native) minus any fees sent to the fee recipient + address payable recipient; + // (deprecated) the merkle root used for proving access + bytes32 merkleRoot; + // max that can be spent in the sale in the base currency + uint256 saleMaximum; + // max that can be spent per user in the base currency + uint256 userMaximum; + // minimum that can be bought in a specific purchase + uint256 purchaseMinimum; + // the time at which the sale starts (users will have an additional random delay if maxQueueTime is set) + uint256 startTime; + // the time at which the sale will end, regardless of tokens raised + uint256 endTime; + // what is the maximum length of time a user could wait in the queue after the sale starts? + uint256 maxQueueTime; + // a link to off-chain information about this sale + string URI; +} + +// Metrics are only updated by the buyWithToken() and buyWithNative() functions +struct Metrics { + // number of purchases + uint256 purchaseCount; + // number of buyers + uint256 buyerCount; + // amount bought denominated in a base currency + uint256 purchaseTotal; + // amount bought for each user denominated in a base currency + mapping(address => uint256) buyerTotal; +} + +struct PaymentTokenInfo { + IOracleOrL2OracleWithSequencerCheck oracle; + uint256 heartbeat; + uint8 decimals; +} + +contract FlatPriceSale_v_4_0 is Sale, PullPaymentUpgradeable, AccessVerifier { + using Address for address payable; + using SafeERC20Upgradeable for IERC20Upgradeable; + + event ImplementationConstructor(INetworkConfig networkConfig); + event Update(Config config); + event Initialize( + Config config, + string baseCurrency, + IOracleOrL2OracleWithSequencerCheck nativeOracle, + bool nativePaymentsEnabled + ); + event SetPaymentTokenInfo( + IERC20Upgradeable token, + PaymentTokenInfo paymentTokenInfo + ); + event SweepToken(address indexed token, uint256 amount); + event SweepNative(uint256 amount); + event RegisterDistributor(address distributor); + + // All supported chains must use 18 decimals (e.g. 1e18 wei / eth) + uint256 internal constant NATIVE_TOKEN_DECIMALS = 18; + + // flag for additional merkle root data + uint8 internal constant PER_USER_PURCHASE_LIMIT = 1; + uint8 internal constant PER_USER_END_TIME = 2; + + /** + Variables set by implementation contract constructor (immutable) + */ + + // denominator used to determine size of fee bips + uint256 constant fractionDenominator = 10000; + + // an optional address where buyers can receive distributed tokens + address distributor; + + /** + Variables set during initialization of clone contracts ("immutable" on each instance) + */ + + // the base currency being used, e.g. 'USD' + string public baseCurrency; + + string public constant VERSION = "4.0"; + + // / price, e.g. ETH/USD price + IOracleOrL2OracleWithSequencerCheck public nativeTokenPriceOracle; + + // heartbeat value for oracle + uint256 public nativeTokenPriceOracleHeartbeat; + + // whether native payments are enabled (set during intialization) + bool nativePaymentsEnabled; + + // / price oracles, eg USDC address => ETH/USDC price + mapping(IERC20Upgradeable => PaymentTokenInfo) public paymentTokens; + + // owner can update these + Config public config; + + // derived from payments + Metrics public metrics; + + // reasonably random value: xor of merkle root and blockhash for transaction setting merkle root + uint160 internal randomValue; + + INetworkConfig public immutable networkConfig; + + // All clones will share the information in the implementation constructor + constructor(address _networkConfig) { + networkConfig = INetworkConfig(_networkConfig); + + emit ImplementationConstructor(networkConfig); + } + + /** + Replacement for constructor for clones of the implementation contract + Important: anyone can call the initialize function! + */ + function initialize( + address _owner, + Config calldata _config, + string calldata _baseCurrency, + bool _nativePaymentsEnabled, + IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, + uint256 _nativeTokenPriceOracleHeartbeat, + IERC20Upgradeable[] calldata tokens, + IOracleOrL2OracleWithSequencerCheck[] calldata oracles, + uint256[] calldata oracleHeartbeats, + uint8[] calldata decimals + ) public initializer validUpdate(_config) { + // initialize the PullPayment escrow contract + __PullPayment_init(); + + __ReentrancyGuard_init(); + + // validate the new sale + require(tokens.length == oracles.length, "token and oracle lengths !="); + require( + tokens.length == decimals.length, + "token and decimals lengths !=" + ); + require( + address(_nativeTokenPriceOracle) != address(0), + "native oracle == 0" + ); + + // save the new sale + config = _config; + + // save payment config + baseCurrency = _baseCurrency; + nativeTokenPriceOracle = _nativeTokenPriceOracle; + nativeTokenPriceOracleHeartbeat = _nativeTokenPriceOracleHeartbeat; + nativePaymentsEnabled = _nativePaymentsEnabled; + emit Initialize( + config, + baseCurrency, + nativeTokenPriceOracle, + _nativePaymentsEnabled + ); + + for (uint256 i = 0; i < tokens.length; i++) { + // double check that tokens and oracles are real addresses + require(address(tokens[i]) != address(0), "payment token == 0"); + require(address(oracles[i]) != address(0), "token oracle == 0"); + // save the payment token info + paymentTokens[tokens[i]] = PaymentTokenInfo({ + oracle: oracles[i], + heartbeat: oracleHeartbeats[i], + decimals: decimals[i] + }); + + emit SetPaymentTokenInfo(tokens[i], paymentTokens[tokens[i]]); + } + + // Set the random value for the fair queue time + randomValue = generatePseudorandomValue(); + + // transfer ownership to the user initializing the sale + _transferOwnership(_owner); + } + + /** + Check that the user can currently participate in the sale based on the access signature + */ + modifier canAccessSale( + uint256 userLimit, + uint64 expiresAt, + bytes memory signature + ) { + // make sure the buyer is an EOA + require((_msgSender() == tx.origin), "Must buy with an EOA"); + + verifyAccessSignature( + networkConfig.getAccessAuthorityAddress(), + _msgSender(), + userLimit, + expiresAt, + signature + ); + + // Require the sale to be open + require(block.timestamp > config.startTime, "sale has not started yet"); + require(block.timestamp < config.endTime, "sale has ended"); + require( + metrics.purchaseTotal < config.saleMaximum, + "sale buy limit reached" + ); + + // Reduce congestion by randomly assigning each user a delay time in a virtual queue based on comparing their address and a random value + // if config.maxQueueTime == 0 the delay is 0 + require( + block.timestamp - config.startTime > getFairQueueTime(_msgSender()), + "not your turn yet" + ); + _; + } + + /** + Check that the new sale is a valid update + - If the config already exists, it must not be over (cannot edit sale after it concludes) + - Sale start, end, and max queue times must be consistent and not too far in the future + */ + modifier validUpdate(Config calldata newConfig) { + // get the existing config + Config memory oldConfig = config; + + /** + - @notice - Block updates after sale is over + - @dev - Since validUpdate is called by initialize(), we can have a new + - sale here, identifiable by default randomValue of 0 + */ + if (randomValue != 0) { + // this is an existing sale: cannot update after it has ended + require( + block.timestamp < oldConfig.endTime, + "sale is over: cannot upate" + ); + if (block.timestamp > oldConfig.startTime) { + // the sale has already started, some things should not be edited + require( + oldConfig.saleMaximum == newConfig.saleMaximum, + "editing saleMaximum after sale start" + ); + } + } + + // the total sale limit must be at least as large as the per-user limit + + // all required values must be present and reasonable + // check if the caller accidentally entered a value in milliseconds instead of seconds + require( + newConfig.startTime <= 4102444800, + "start > 4102444800 (Jan 1 2100)" + ); + require( + newConfig.endTime <= 4102444800, + "end > 4102444800 (Jan 1 2100)" + ); + require( + newConfig.maxQueueTime <= 604800, + "max queue time > 604800 (1 week)" + ); + require(newConfig.recipient != address(0), "recipient == address(0)"); + + // sale, user, and purchase limits must be compatible + require(newConfig.saleMaximum > 0, "saleMaximum == 0"); + require(newConfig.userMaximum > 0, "userMaximum == 0"); + require( + newConfig.userMaximum <= newConfig.saleMaximum, + "userMaximum > saleMaximum" + ); + require( + newConfig.purchaseMinimum <= newConfig.userMaximum, + "purchaseMinimum > userMaximum" + ); + + // new sale times must be internally consistent + require( + newConfig.startTime + newConfig.maxQueueTime < newConfig.endTime, + "sale must be open for at least maxQueueTime" + ); + + _; + } + + modifier validPaymentToken(IERC20Upgradeable token) { + // check that this token is configured as a payment method + PaymentTokenInfo memory info = paymentTokens[token]; + require(address(info.oracle) != address(0), "invalid payment token"); + + _; + } + + modifier areNativePaymentsEnabled() { + require(nativePaymentsEnabled, "native payments disabled"); + + _; + } + + // Get info on a payment token + function getPaymentToken( + IERC20Upgradeable token + ) external view returns (PaymentTokenInfo memory) { + return paymentTokens[token]; + } + + // TODO: reduce duplication between other contracts + // Get a positive token price from a chainlink oracle + function getOraclePrice( + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256) { + (, int256 _price, , uint256 updatedAt, uint80 answeredInRound) = oracle + .latestRoundData(); + + require(_price > 0, "negative price"); + require(answeredInRound > 0, "answer == 0"); + require(updatedAt > 0, "round not complete"); + require( + updatedAt > block.timestamp - heartbeat, + "stale price due to heartbeat" + ); + + return uint256(_price); + } + + /** + Generate a pseudorandom value + This is not a truly random value: + - miners can alter the block hash + - owners can repeatedly call setMerkleRoot() + - owners can choose when to submit the transaction + */ + function generatePseudorandomValue() public view returns (uint160) { + return uint160(uint256(blockhash(block.number - 1))); + } + + /** + Get the delay in seconds that a specific buyer must wait after the sale begins in order to buy tokens in the sale + + Buyers cannot exploit the fair queue when: + - The sale is private (merkle root != bytes32(0)) + - Each eligible buyer gets exactly one address in the merkle root + + Although miners and sellers can minimize the delay for an arbitrary address, these are not significant threats: + - the economic opportunity to miners is zero or relatively small (only specific addresses can participate in private sales, and a better queue postion does not imply high returns) + - sellers can repeatedly set merkle roots to achieve a favorable queue time for any address, but sellers already control the tokens being sold! + */ + function getFairQueueTime(address buyer) public view returns (uint256) { + if (config.maxQueueTime == 0) { + // there is no delay: all addresses may participate immediately + return 0; + } + + // calculate a distance between the random value and the user's address using the XOR distance metric (c.f. Kademlia) + uint160 distance = uint160(buyer) ^ randomValue; + + // calculate a speed at which the queue is exhausted such that all users complete the queue by sale.maxQueueTime + uint160 distancePerSecond = type(uint160).max / + uint160(config.maxQueueTime); + // return the delay (seconds) + return distance / distancePerSecond; + } + + /** + Convert a token quantity (e.g. USDC or ETH) to a base currency (e.g. USD) with the same number of decimals as the price oracle (e.g. 8) + + Example: given 2 NCT tokens, each worth $1.23, tokensToBaseCurrency should return 246000000 ($2.46) + + Function arguments + - tokenQuantity: 2000000000000000000 + - tokenDecimals: 18 + + NCT/USD chainlink oracle (important! the oracle must be / not /, e.g. ETH/USD, ~$2000 not USD/ETH, ~0.0005) + - baseCurrencyPerToken: 123000000 + - baseCurrencyDecimals: 8 + + Calculation: 2000000000000000000 * 123000000 / 1000000000000000000 + + Returns: 246000000 + */ + // function tokensToBaseCurrency(SafeERC20Upgradeable token, uint256 quantity) public view validPaymentToken(token) returns (uint256) { + // PaymentTokenInfo info = paymentTokens[token]; + // return quantity * getOraclePrice(info.oracle) / (10 ** info.decimals); + // } + // TODO: reduce duplication between other contracts + function tokensToBaseCurrency( + uint256 tokenQuantity, + uint256 tokenDecimals, + IOracleOrL2OracleWithSequencerCheck oracle, + uint256 heartbeat + ) public view returns (uint256 value) { + return + (tokenQuantity * getOraclePrice(oracle, heartbeat)) / + (10 ** tokenDecimals); + } + + function total() external view override returns (uint256) { + return metrics.purchaseTotal; + } + + function isOver() public view override returns (bool) { + return + config.endTime <= block.timestamp || + metrics.purchaseTotal >= config.saleMaximum; + } + + function isOpen() public view override returns (bool) { + return + config.startTime < block.timestamp && + config.endTime > block.timestamp && + metrics.purchaseTotal < config.saleMaximum; + } + + // return the amount bought by this user in base currency + function buyerTotal(address user) external view override returns (uint256) { + return metrics.buyerTotal[user]; + } + + /** + Records a purchase + Follow the Checks -> Effects -> Interactions pattern + * Checks: CALLER MUST ENSURE BUYER IS PERMITTED TO PARTICIPATE IN THIS SALE: THIS METHOD DOES NOT CHECK WHETHER THE BUYER SHOULD BE ABLE TO ACCESS THE SALE! + * Effects: record the payment + * Interactions: none! + */ + function _execute( + uint256 baseCurrencyQuantity, + uint256 userLimit + ) internal { + require( + baseCurrencyQuantity + metrics.buyerTotal[_msgSender()] <= + userLimit, + "purchase exceeds your limit" + ); + + require( + baseCurrencyQuantity + metrics.purchaseTotal <= config.saleMaximum, + "purchase exceeds sale limit" + ); + + require( + baseCurrencyQuantity >= config.purchaseMinimum, + "purchase under minimum" + ); + + // Effects + metrics.purchaseCount += 1; + if (metrics.buyerTotal[_msgSender()] == 0) { + // if no prior purchases, this is a new buyer + metrics.buyerCount += 1; + } + metrics.purchaseTotal += baseCurrencyQuantity; + metrics.buyerTotal[_msgSender()] += baseCurrencyQuantity; + } + + /** + Settle payment made with payment token + Important: this function has no checks! Only call if the purchase is valid! + */ + function _settlePaymentToken( + uint256 baseCurrencyValue, + IERC20Upgradeable token, + uint256 quantity, + uint256 feeLevel, + uint256 platformFlatRateFeeAmount + ) internal { + uint256 fee = (quantity * feeLevel) / fractionDenominator; + token.safeTransferFrom( + _msgSender(), + networkConfig.getFeeRecipient(), + fee + ); + token.safeTransferFrom(_msgSender(), address(this), quantity - fee); + emit Buy( + _msgSender(), + address(token), + baseCurrencyValue, + quantity, + fee, + platformFlatRateFeeAmount + ); + } + + /** + Settle payment made with native token + Important: this function has no checks! Only call if the purchase is valid! + */ + function _settleNativeToken( + uint256 baseCurrencyValue, + uint256 nativeTokenQuantity, + uint256 feeLevel, + uint256 platformFlatRateFeeAmount + ) internal { + uint256 nativeFee = (nativeTokenQuantity * feeLevel) / + fractionDenominator; + _asyncTransfer(networkConfig.getFeeRecipient(), nativeFee); + _asyncTransfer( + config.recipient, + nativeTokenQuantity - nativeFee - platformFlatRateFeeAmount + ); + + // This contract will hold the native token until claimed by the owner + emit Buy( + _msgSender(), + address(0), + baseCurrencyValue, + nativeTokenQuantity, + nativeFee, + platformFlatRateFeeAmount + ); + } + + /** + Pay with the payment token (e.g. USDC) + */ + function buyWithToken( + IERC20Upgradeable token, + uint256 quantity, + uint256 userLimit, + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) + external + payable + override + canAccessSale(userLimit, expiresAt, signature) + validPaymentToken(token) + nonReentrant + { + uint256 nativeBaseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require( + nativeBaseCurrencyValue >= platformFlatRateFeeAmount, + "fee payment below minimum" + ); + + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * + (10 ** NATIVE_TOKEN_DECIMALS)) / + getOraclePrice( + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + )); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + + // convert to base currency from payment tokens + PaymentTokenInfo memory tokenInfo = paymentTokens[token]; + uint256 baseCurrencyValue = tokensToBaseCurrency( + quantity, + tokenInfo.decimals, + tokenInfo.oracle, + tokenInfo.heartbeat + ); + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge( + networkConfig.getStakingAddress() + ); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + + // Checks and Effects + _execute(baseCurrencyValue + platformFlatRateFeeAmount, userLimit); + // Interactions + _settlePaymentToken( + baseCurrencyValue + platformFlatRateFeeAmount, + token, + quantity, + feeLevel, + platformFlatRateFeeAmount + ); + } + + /** + Pay with the native token (e.g. ETH) + */ + function buyWithNative( + uint256 userLimit, + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) + external + payable + override + canAccessSale(userLimit, expiresAt, signature) + areNativePaymentsEnabled + nonReentrant + { + // convert to base currency from native tokens + // converts msg.value (which is how much ETH was sent by user) to USD + // Example: On September 1st, 2024 + // 0.005 * 2450**10e8 => ~$10 = how much in USD represents + // msg.value + uint256 baseCurrencyValue = tokensToBaseCurrency( + msg.value, + NATIVE_TOKEN_DECIMALS, + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + ); + + require( + baseCurrencyValue >= platformFlatRateFeeAmount, + "fee payment below minimum" + ); + + // Example: On September 1st, 2024 + // 1/2450 * 10**18 = how much ETH (in wei) we need to represent + // 1 USD + uint256 feeAmountInWei = ((platformFlatRateFeeAmount * + (10 ** NATIVE_TOKEN_DECIMALS)) / + getOraclePrice( + nativeTokenPriceOracle, + nativeTokenPriceOracleHeartbeat + )); + + platformFlatRateFeeRecipient.sendValue(feeAmountInWei); + + IFeeLevelJudge feeLevelJudge = IFeeLevelJudge( + networkConfig.getStakingAddress() + ); + uint256 feeLevel = feeLevelJudge.getFeeLevel(_msgSender()); + if (feeLevel == 0) { + feeLevel = 100; + } + // Checks and Effects + _execute(baseCurrencyValue, userLimit); + // Interactions + _settleNativeToken( + baseCurrencyValue, + msg.value, + feeLevel, + feeAmountInWei + ); + } + + /** + External management functions (only the owner may update the sale) + */ + function update( + Config calldata _config + ) external validUpdate(_config) onlyOwner { + config = _config; + // updates always reset the random value + randomValue = generatePseudorandomValue(); + emit Update(config); + } + + // Tell users where they can claim tokens + function registerDistributor(address _distributor) external onlyOwner { + require(_distributor != address(0), "Distributor == address(0)"); + distributor = _distributor; + emit RegisterDistributor(distributor); + } + + /** + Public management functions + */ + // Sweep an ERC20 token to the recipient (public function) + function sweepToken(IERC20Upgradeable token) external { + uint256 amount = token.balanceOf(address(this)); + token.safeTransfer(config.recipient, amount); + emit SweepToken(address(token), amount); + } + + // sweep native token to the recipient (public function) + function sweepNative() external { + uint256 amount = address(this).balance; + (bool success, ) = config.recipient.call{ value: amount }(""); + require(success, "Transfer failed."); + emit SweepNative(amount); + } +} diff --git a/zksync-ts/contracts/sale/v4/FlatPriceSaleFactory.sol b/zksync-ts/contracts/sale/v4/FlatPriceSaleFactory.sol new file mode 100644 index 00000000..2b18782b --- /dev/null +++ b/zksync-ts/contracts/sale/v4/FlatPriceSaleFactory.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.21; + +import "@openzeppelin/contracts/proxy/Clones.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "./FlatPriceSale.sol"; +import "./Proxy.sol"; + +contract FlatPriceSaleFactory_v_4_0 is Ownable { + address public implementation; + string public constant VERSION = "4.0"; + + event NewSale( + address indexed implementation, + FlatPriceSale_v_4_0 indexed clone, + Config config, + string baseCurrency, + IOracleOrL2OracleWithSequencerCheck nativeOracle, + uint256 nativeOracleHeartbeat, + bool nativePaymentsEnabled + ); + + constructor(address _implementation) { + implementation = _implementation; + } + + function upgradeFutureSales(address _implementation) external onlyOwner { + implementation = _implementation; + } + + function newSale( + address _owner, + Config calldata _config, + string calldata _baseCurrency, + bool _nativePaymentsEnabled, + IOracleOrL2OracleWithSequencerCheck _nativeTokenPriceOracle, + uint256 _nativeTokenPriceOracleHeartbeat, + IERC20Upgradeable[] calldata tokens, + IOracleOrL2OracleWithSequencerCheck[] calldata oracles, + uint256[] calldata oracleHeartbeats, + uint8[] calldata decimals + ) external returns (FlatPriceSale_v_4_0 sale) { + sale = FlatPriceSale_v_4_0(address(new Proxy(address(implementation)))); + + emit NewSale( + implementation, + sale, + _config, + _baseCurrency, + _nativeTokenPriceOracle, + _nativeTokenPriceOracleHeartbeat, + _nativePaymentsEnabled + ); + + sale.initialize( + _owner, + _config, + _baseCurrency, + _nativePaymentsEnabled, + _nativeTokenPriceOracle, + _nativeTokenPriceOracleHeartbeat, + tokens, + oracles, + oracleHeartbeats, + decimals + ); + } +} diff --git a/zksync-ts/contracts/sale/v4/Proxy.sol b/zksync-ts/contracts/sale/v4/Proxy.sol new file mode 100644 index 00000000..0bc5ca27 --- /dev/null +++ b/zksync-ts/contracts/sale/v4/Proxy.sol @@ -0,0 +1,35 @@ +pragma solidity =0.8.21; + +contract Proxy { + address immutable implementation; + + constructor(address _implementation) { + implementation = _implementation; + } + + fallback() external payable { + address impl = implementation; + assembly { + // The pointer to the free memory slot + let ptr := mload(0x40) + // Copy function signature and arguments from calldata at zero position into memory at pointer position + calldatacopy(ptr, 0, calldatasize()) + // Delegatecall method of the implementation contract returns 0 on error + let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0) + // Get the size of the last return data + let size := returndatasize() + // Copy the size length of bytes from return data at zero position to pointer position + returndatacopy(ptr, 0, size) + // Depending on the result value + switch result + case 0 { + // End execution and revert state changes + revert(ptr, size) + } + default { + // Return data with length of size at pointers position + return(ptr, size) + } + } + } +} diff --git a/zksync-ts/contracts/sale/v4/Sale.sol b/zksync-ts/contracts/sale/v4/Sale.sol new file mode 100644 index 00000000..58c9d587 --- /dev/null +++ b/zksync-ts/contracts/sale/v4/Sale.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol"; + +// Upgradeable contracts are required to use clone() in SaleFactory +abstract contract Sale is ReentrancyGuardUpgradeable, OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + event Buy( + address indexed buyer, + address indexed token, + uint256 baseCurrencyValue, + uint256 tokenValue, + uint256 protocolTokenFee, + uint256 platformTokenFee + ); + + /** + Important: the constructor is only called once on the implementation contract (which is never initialized) + Clones using this implementation cannot use this constructor method. + Thus every clone must use the same fields stored in the constructor (feeBips, feeRecipient) + */ + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function buyWithToken( + IERC20Upgradeable token, + uint256 quantity, + uint256 userLimit, + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) external payable virtual {} + + function buyWithNative( + uint256 userLimit, + uint64 expiresAt, + bytes memory signature, + address payable platformFlatRateFeeRecipient, + uint256 platformFlatRateFeeAmount + ) external payable virtual {} + + function isOpen() public view virtual returns (bool) {} + + function isOver() public view virtual returns (bool) {} + + function buyerTotal(address user) external view virtual returns (uint256) {} + + function total() external view virtual returns (uint256) {} +} diff --git a/zksync-ts/contracts/sale/v4/Test_FlatPriceSale.sol b/zksync-ts/contracts/sale/v4/Test_FlatPriceSale.sol new file mode 100644 index 00000000..66ee3f89 --- /dev/null +++ b/zksync-ts/contracts/sale/v4/Test_FlatPriceSale.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; +// pragma abicoder v2; + +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import "../../config/INetworkConfig.sol"; +import "../../utilities/AccessVerifier.sol"; +import "@openzeppelin/contracts-upgradeable/security/PullPaymentUpgradeable.sol"; + +contract Test_FlatPriceSale { + using Address for address payable; + + string public constant VERSION = "0.0.1"; + + event OutputOne( + address accessAuthorityAddress, + address sender, + uint256 userLimit, + uint64 expiresAt, + bytes signature, + address contractAddress, + bytes message, + bytes32 hash, + address signer + ); + + /** + Pay with the native token (e.g. ETH). + */ + function buyWithNative( + uint256 userLimit, + uint64 expiresAt, + bytes memory signature // Remove 'view' modifier as we're now emitting an event + ) external { + INetworkConfig networkConfig = INetworkConfig( + 0xe6d4e4d7741e556B2b6123973318B01e5d202831 + ); + bytes memory message = abi.encodePacked( + address(this), + msg.sender, + userLimit, + expiresAt + ); + bytes32 hash = keccak256(message); + bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(hash); + address signer = ECDSA.recover(ethSignedMessageHash, signature); + + emit OutputOne( + networkConfig.getAccessAuthorityAddress(), + msg.sender, + userLimit, + expiresAt, + signature, + address(this), + message, + hash, + signer + ); + } +} diff --git a/zksync-ts/contracts/token/ERC20.sol b/zksync-ts/contracts/token/ERC20.sol new file mode 100644 index 00000000..2242a35a --- /dev/null +++ b/zksync-ts/contracts/token/ERC20.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract GenericERC20 is ERC20 { + uint8 d; + + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals, + uint256 supply + ) ERC20(_name, _symbol) { + d = _decimals; + _mint(msg.sender, supply); + } + + function decimals() public view virtual override returns (uint8) { + return d; + } +} + +contract FakeUSDC is ERC20 { + uint8 d; + + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals, + uint256 supply + ) ERC20(_name, _symbol) { + d = _decimals; + _mint(msg.sender, supply); + } + + function decimals() public view virtual override returns (uint8) { + return d; + } +} + +contract FakeUSDT is ERC20 { + uint8 d; + + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals, + uint256 supply + ) ERC20(_name, _symbol) { + d = _decimals; + _mint(msg.sender, supply); + } + + function decimals() public view virtual override returns (uint8) { + return d; + } +} diff --git a/zksync-ts/contracts/token/ERC20Votes.sol b/zksync-ts/contracts/token/ERC20Votes.sol new file mode 100644 index 00000000..375c78a9 --- /dev/null +++ b/zksync-ts/contracts/token/ERC20Votes.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; + +contract MyERC20Votes is ERC20, ERC20Permit, ERC20Votes { + constructor( + string memory _name, + string memory _symbol, + uint256 supply + ) ERC20(_name, _symbol) ERC20Permit(_name) { + _mint(msg.sender, supply); + } + + // The following functions are overrides required by Solidity. + + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal override(ERC20, ERC20Votes) { + super._afterTokenTransfer(from, to, amount); + } + + function _mint(address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._mint(to, amount); + } + + function _burn(address account, uint256 amount) internal override(ERC20, ERC20Votes) { + super._burn(account, amount); + } +} diff --git a/zksync-ts/contracts/trade/Trader.sol b/zksync-ts/contracts/trade/Trader.sol new file mode 100644 index 00000000..1c29fc8f --- /dev/null +++ b/zksync-ts/contracts/trade/Trader.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IHashflowQuote} from "../interfaces/IHashflowQuote.sol"; +import {Sweepable} from "../utilities/Sweepable.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; + +contract Trader is IHashflowQuote, Ownable, Sweepable, ReentrancyGuard { + using SafeERC20 for IERC20; + + event SetConfig( + IHashflowQuote router, + uint256 feeBips + ); + + event HashflowTradeSingleHop(IHashflowQuote.RFQTQuote quote); + event HashflowTradeXChain(IHashflowQuote.XChainRFQTQuote quote, XChainMessageProtocol protocol); + + IHashflowQuote private router; + uint256 private feeBips; + + constructor(IHashflowQuote _router, uint256 _feeBips, address payable _feeRecipient) Sweepable(_feeRecipient) { + _setConfig(_router, _feeBips, _feeRecipient); + } + + function getSplit(uint256 initialTotal) public view returns(uint256 baseTokenTotal, uint256 baseTokenAmount, uint256 baseTokenFee) { + // calculate total, base, and fee token amounts exactly like they will be calculated during the trade + baseTokenAmount = initialTotal * (10000 - feeBips) / 10000; + baseTokenFee = getFee(baseTokenAmount); + baseTokenTotal = baseTokenAmount + baseTokenFee; + return (baseTokenTotal, baseTokenAmount, baseTokenFee); + } + + function getFee(uint256 baseTokenAmount) public view returns(uint256 baseTokenFee) { + baseTokenFee = baseTokenAmount * feeBips / (10000 - feeBips); + } + + function tradeSingleHop(RFQTQuote calldata quote) public payable nonReentrant { + // calculate the trade fee + uint256 fee = getFee(quote.effectiveBaseTokenAmount); + + // transfer fee + effectiveBaseTokenAmount to contract so it can then transfer effectiveBaseTokenAmount to the maker + if (quote.baseToken != address(0)) { + // this is an ERC20 transfer: get all ERC20 tokens and approve the Hashflow router to withdraw the correct quantity + IERC20(quote.baseToken).safeTransferFrom(msg.sender, address(this), quote.effectiveBaseTokenAmount + fee); + IERC20(quote.baseToken).approve(address(router), quote.effectiveBaseTokenAmount); + router.tradeSingleHop(quote); + } else { + // this is a native token transfer + require(msg.value == quote.effectiveBaseTokenAmount + fee, "incorrect value"); + // low level call to send along ETH in the internal tx + (bool success,) = address(router).call{value: quote.effectiveBaseTokenAmount}(abi.encodeWithSignature("tradeSingleHop((address,address,address,address,address,address,uint256,uint256,uint256,uint256,uint256,bytes32,bytes))", quote)); + require(success, "native base token trade failed"); + } + + emit HashflowTradeSingleHop(quote); + } + + function tradeXChain( + XChainRFQTQuote calldata quote, + XChainMessageProtocol protocol + ) public payable nonReentrant { + // calculate the trade fee + uint256 fee = getFee(quote.baseTokenAmount); + + // transfer the trade fee to the fee recipient + if (quote.baseToken != address(0)) { + // this is an ERC20 transfer - pull in the base token including fee and approve Hashflow to pull the baseTokenAmount + IERC20(quote.baseToken).safeTransferFrom(msg.sender, address(this), fee + quote.baseTokenAmount); + IERC20(quote.baseToken).approve(address(router), quote.baseTokenAmount); + + // NOTE: for ERC20 base token trades, msg.value == xChainFeeEstimate (the fee is in ERC20) + router.tradeXChain{value: msg.value}(quote, protocol); + } else { + // this is a native token transfer + // NOTE: for native base token trades, msg.value == xChainFeeEstimate + quote.baseTokenAmount + fee + router.tradeXChain{value: msg.value - fee}(quote, protocol); + } + emit HashflowTradeXChain(quote, protocol); + } + + function _setConfig( + IHashflowQuote _router, + uint256 _feeBips, + address payable _feeRecipient + ) private { + router = _router; + feeBips = _feeBips; + _setSweepRecipient(_feeRecipient); + emit SetConfig(router, feeBips); + } + + function setConfig ( + IHashflowQuote _router, + uint256 _feeBips, + address payable _feeRecipient + ) external onlyOwner { + _setConfig(_router, _feeBips, _feeRecipient); + } + + function getConfig () external view returns (IHashflowQuote, uint256, address payable) { + return (router, feeBips, getSweepRecipient()); + } +} diff --git a/zksync-ts/contracts/utilities/AccessVerifier.sol b/zksync-ts/contracts/utilities/AccessVerifier.sol new file mode 100644 index 00000000..32cc7a4d --- /dev/null +++ b/zksync-ts/contracts/utilities/AccessVerifier.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.0.0 +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +contract AccessVerifier { + function verifyAccessSignature( + address accessAuthorityAddress, + address member, + uint256 userLimit, + uint64 expires_at, + bytes memory signature + ) internal view { + bytes memory message = abi.encodePacked( + address(this), + member, + userLimit, + expires_at + ); + bytes32 hash = keccak256(message); + bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(hash); + address signer = ECDSA.recover(ethSignedMessageHash, signature); + + // TODO: use custom error objects with a revert statement + require(signer == accessAuthorityAddress, "access signature invalid"); + require(block.timestamp < expires_at, "access signature expired"); + } +} diff --git a/zksync-ts/contracts/utilities/BatchSendEth.sol b/zksync-ts/contracts/utilities/BatchSendEth.sol new file mode 100644 index 00000000..c96622a6 --- /dev/null +++ b/zksync-ts/contracts/utilities/BatchSendEth.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +contract BatchSendEth { + constructor() {} + + function send(address[] calldata addresses, uint256 amount) public payable { + for (uint256 i = addresses.length; i != 0; ) { + unchecked { + --i; + } + + addresses[i].call{ value: amount }(""); + } + } +} diff --git a/zksync-ts/contracts/utilities/FairQueue.sol b/zksync-ts/contracts/utilities/FairQueue.sol new file mode 100644 index 00000000..8c1ee59e --- /dev/null +++ b/zksync-ts/contracts/utilities/FairQueue.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +/** + * @title FairQueue + * @notice Fairly assigns a delay time to each address from a uniform distribution over [0, maxDelayTime] + * @dev The delay is determined by calculating a distance between the user's address and a pseudorandom value based on a provided salt and a blockhash + * using the XOR distance metric. Do not use this contract if the event is public because users could grind addresses until they find one with a low delay. + */ +contract FairQueue { + event SetDelay(uint160 maxDelayTime); + /** + * calculate a speed at which the queue is exhausted such that all users complete the queue by maxDelayTime + */ + uint160 public distancePerSecond; + uint160 public maxDelayTime; + + /** + * @dev the random value from which a distance will be calculated for each address. Reset the random value + * to shuffle the delays for all addresses. + */ + uint160 public randomValue; + + constructor(uint160 _maxDelayTime, uint160 salt) { + _setDelay(_maxDelayTime); + _setPseudorandomValue(salt); + } + + /** + * @dev internal function to set the random value. A salt (e.g. from a merkle root) is required to prevent + * naive manipulation of the random value by validators + */ + function _setPseudorandomValue(uint160 salt) internal { + if (distancePerSecond == 0) { + // there is no delay: random value is not needed + return; + } + require(salt > 0, 'I demand more randomness'); + randomValue = uint160(uint256(blockhash(block.number - 1))) ^ salt; + } + + /** + @dev Internal function to configure delay + @param _maxDelayTime the maximum delay for any address in seconds. Set this value to 0 to disable delays entirely. + */ + function _setDelay(uint160 _maxDelayTime) internal { + maxDelayTime = _maxDelayTime; + distancePerSecond = _maxDelayTime > 0 ? type(uint160).max / _maxDelayTime : 0; + emit SetDelay(_maxDelayTime); + } + + /** + @notice get a fixed delay for any address by drawing from a unform distribution over the interval [0, maxDelay] + @param user The address for which a delay should be calculated. The delay is deterministic for any given address and pseudorandom value. + @dev The delay is determined by calculating a distance between the user's address and a pseudorandom value using the XOR distance metric (c.f. Kademlia) + + Users cannot exploit the fair delay if: + - The event is private, i.e. an access list of some form is required + - Each eligible user gets exactly one address in the access list + - There is no collusion between event participants, block validators, and event owners + + The threat of collusion is likely minimal: + - the economic opportunity to validators is zero or relatively small (only specific addresses can participate in private events, and a lower delay time does not imply higher returns) + - event owners are usually trying to achieve a fair distribution of access to their event + */ + function getFairDelayTime(address user) public view returns (uint256) { + if (distancePerSecond == 0) { + // there is no delay: all addresses may participate immediately + return 0; + } + + // calculate a distance between the random value and the user's address using the XOR distance metric (c.f. Kademlia) + uint160 distance = uint160(user) ^ randomValue; + + // return the delay (seconds) + return distance / distancePerSecond; + } +} diff --git a/zksync-ts/contracts/utilities/L2OracleWithSequencerCheck.sol b/zksync-ts/contracts/utilities/L2OracleWithSequencerCheck.sol new file mode 100644 index 00000000..66977aaa --- /dev/null +++ b/zksync-ts/contracts/utilities/L2OracleWithSequencerCheck.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "../interfaces/IOracleOrL2OracleWithSequencerCheck.sol"; + +/** + * @title L2OracleWithSequencerCheck + * @author + * @notice Data feed oracle for use on optimistic L2s (Arbiturm, Optimism, Base, + * Metis) that uses a data feed that tracks the last known status of the + * L2 sequencer at a given point in time, and reverts if the sequencer is + * down, or is up but a specified grace period has not passed. + * + * @dev For a list of available Sequencer Uptime Feed proxy addresses, see: + * https://docs.chain.link/docs/data-feeds/l2-sequencer-feeds + */ +contract L2OracleWithSequencerCheck is IOracleOrL2OracleWithSequencerCheck { + AggregatorV3Interface internal dataFeed; + AggregatorV3Interface internal sequencerUptimeFeed; + + uint256 private constant GRACE_PERIOD_TIME = 3600; + + error SequencerDown(); + error GracePeriodNotOver(); + + constructor(address _dataFeed, address _sequencerUptimeFeed) { + dataFeed = AggregatorV3Interface( + _dataFeed + ); + sequencerUptimeFeed = AggregatorV3Interface( + _sequencerUptimeFeed + ); + } + + //// @dev Checks the sequencer status and returns the latest data + function latestRoundData() public view returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) { + ( + /*uint80 roundID*/, + int256 _answer, + uint256 _startedAt, + /*uint256 updatedAt*/, + /*uint80 answeredInRound*/ + ) = sequencerUptimeFeed.latestRoundData(); + + // Answer == 0: Sequencer is up + // Answer == 1: Sequencer is down + bool isSequencerUp = _answer == 0; + if (!isSequencerUp) { + revert SequencerDown(); + } + + // Make sure the grace period has passed after the + // sequencer is back up. + uint256 timeSinceUp = block.timestamp - _startedAt; + if (timeSinceUp <= GRACE_PERIOD_TIME) { + revert GracePeriodNotOver(); + } + + return dataFeed.latestRoundData(); + } + + function decimals() public view returns (uint8) { + return dataFeed.decimals(); + } +} diff --git a/zksync-ts/contracts/utilities/Registry.sol b/zksync-ts/contracts/utilities/Registry.sol new file mode 100644 index 00000000..e8a3c1c1 --- /dev/null +++ b/zksync-ts/contracts/utilities/Registry.sol @@ -0,0 +1,63 @@ +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/access/AccessControl.sol"; + +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +contract Registry is AccessControl, Ownable { + event DeployRegistry(); + event Register(address indexed addressRegistered, bytes4[] interfaceIds, address registeredBy); + event Unregister( + address indexed addressUnregistered, + bytes4[] interfaceIds, + address unregisteredBy + ); + + bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); + + // contract address => interface id => supported boolean + mapping(address => mapping(bytes4 => bool)) private interfaces; + + constructor() Ownable() { + emit DeployRegistry(); + } + + function addAdmin(address admin) public onlyOwner { + _grantRole(ADMIN_ROLE, admin); + } + + function removeAdmin(address admin) public onlyOwner { + _revokeRole(ADMIN_ROLE, admin); + } + + function register(address addressRegistered, bytes4[] calldata interfaceIds) + public + onlyRole(ADMIN_ROLE) + { + for (uint256 i = interfaceIds.length; i != 0; ) { + interfaces[addressRegistered][interfaceIds[i - 1]] = true; + unchecked { + --i; + } + } + + emit Register(addressRegistered, interfaceIds, msg.sender); + } + + function unregister(address addressUnregistered, bytes4[] calldata interfaceIds) + public + onlyRole(ADMIN_ROLE) + { + for (uint256 i = interfaceIds.length; i != 0; ) { + interfaces[addressUnregistered][interfaceIds[i - 1]] = false; + unchecked { + --i; + } + } + emit Unregister(addressUnregistered, interfaceIds, msg.sender); + } + + function targetSupportsInterface(address target, bytes4 interfaceId) public view returns (bool) { + return interfaces[target][interfaceId]; + } +} diff --git a/zksync-ts/contracts/utilities/Sweepable.sol b/zksync-ts/contracts/utilities/Sweepable.sol new file mode 100644 index 00000000..c06eb5da --- /dev/null +++ b/zksync-ts/contracts/utilities/Sweepable.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +abstract contract Sweepable is Ownable { + using SafeERC20 for IERC20; + + event SetSweepRecipient(address recipient); + event SweepToken(address indexed token, uint256 amount); + event SweepNative(uint256 amount); + + address payable private recipient; + + constructor(address payable _recipient) { + _setSweepRecipient(_recipient); + } + + // Sweep an ERC20 token to the owner + function sweepToken(IERC20 token) external onlyOwner { + uint256 amount = token.balanceOf(address(this)); + token.safeTransfer(recipient, amount); + emit SweepToken(address(token), amount); + } + + function sweepToken(IERC20 token, uint256 amount) external onlyOwner { + token.safeTransfer(recipient, amount); + emit SweepToken(address(token), amount); + } + + // sweep native token to the recipient (public function) + function sweepNative() external onlyOwner { + uint256 amount = address(this).balance; + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Transfer failed."); + emit SweepNative(amount); + } + + function sweepNative(uint256 amount) external onlyOwner { + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Transfer failed."); + emit SweepNative(amount); + } + + function getSweepRecipient() public view returns (address payable) { + return recipient; + } + + function _setSweepRecipient(address payable _recipient) internal { + recipient = _recipient; + emit SetSweepRecipient(recipient); + } +} \ No newline at end of file diff --git a/zksync-ts/deploy/deploy.ts b/zksync-ts/deploy/deploy.ts new file mode 100644 index 00000000..7c63f905 --- /dev/null +++ b/zksync-ts/deploy/deploy.ts @@ -0,0 +1,145 @@ +import { Addressable, ethers } from "ethers"; +import { deployContract, getWallet } from "./utils"; +import * as hre from "hardhat"; + +async function deployNetworkConfig() { + const networkConfigContractArtifactName = "NetworkConfig"; + const networkConfig = await deployContract( + networkConfigContractArtifactName, + [] + ); + console.log("NetworkConfig deployed to:", networkConfig.target); + + return networkConfig.target; +} + +async function deployFeeLevelJudgeStub() { + const feeLevelJudgeStubContractArtifactName = "FeeLevelJudgeStub"; + const feeLevelJudgeStub = await deployContract( + feeLevelJudgeStubContractArtifactName, + [100] + ); + console.log("FeeLevelJudgeStub deployed to:", feeLevelJudgeStub.target); + + return feeLevelJudgeStub.target; +} + +async function initializeNetworkConfig( + deployedContracts: Record +) { + const networkConfigAddress = deployedContracts.networkConfig; + const feeLevelJudgeStubAddress = deployedContracts.feeLevelJudgeStub; + + const feeRecipient = process.env.NETWORK_CONFIG_FEE_RECIPIENT; + const nativeTokenPriceOracleAddress = + process.env.NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_ADDRESS; + const nativeTokenPriceOracleHeartbeat = + process.env.NETWORK_CONFIG_NATIVE_TOKEN_PRICE_ORACLE_HEARTBEAT; + const accessAuthorityAddress = + process.env.NETWORK_CONFIG_ACCESS_AUTHORITY_ADDRESS; + + if ( + !feeRecipient || + !nativeTokenPriceOracleAddress || + !nativeTokenPriceOracleHeartbeat || + !accessAuthorityAddress + ) { + throw new Error( + "Missing required environment variables for NetworkConfig initialization" + ); + } + + const networkConfigContractArtifact = await hre.artifacts.readArtifact( + "NetworkConfig" + ); + // Initialize contract instance for interaction + const networkConfigContract = new ethers.Contract( + networkConfigAddress, + networkConfigContractArtifact.abi, + getWallet() // Interact with the contract on behalf of this wallet + ); + + const tx = await networkConfigContract.initialize( + feeRecipient, + feeLevelJudgeStubAddress, + nativeTokenPriceOracleAddress, + nativeTokenPriceOracleHeartbeat, + accessAuthorityAddress + ); + console.log(`Transaction hash of initializing NetworkConfig: ${tx.hash}`); + await tx.wait(); + + console.log("NetworkConfig initialized"); +} + +async function deployFlatPriceSaleFactoryV4Module(): Promise< + Record +> { + // Deploy NetworkConfig + const networkConfigAddress = await deployNetworkConfig(); + + // Deploy FlatPriceSale_v_4_0 + const flatPriceSaleV4ContractArtifactName = "FlatPriceSale_v_4_0"; + const flatPriceSaleV4ConstructorArguments = [networkConfigAddress]; + const flatPriceSaleV4 = await deployContract( + flatPriceSaleV4ContractArtifactName, + flatPriceSaleV4ConstructorArguments + ); + console.log("FlatPriceSale_v_4_0 deployed to:", flatPriceSaleV4.target); + + // Deploy FlatPriceSaleFactory_v_4_0 + const flatPriceSaleFactoryV4ContractArtifactName = + "FlatPriceSaleFactory_v_4_0"; + const flatPriceSaleFactoryV4ConstructorArguments = [flatPriceSaleV4.target]; + const flatPriceSaleFactoryV4 = await deployContract( + flatPriceSaleFactoryV4ContractArtifactName, + flatPriceSaleFactoryV4ConstructorArguments + ); + console.log( + "FlatPriceSaleFactory_v_4_0 deployed to:", + flatPriceSaleFactoryV4.target + ); + + return { + networkConfig: networkConfigAddress, + flatPriceSaleV4: flatPriceSaleV4.target, + flatPriceSaleFactoryV4: flatPriceSaleFactoryV4.target + }; +} + +async function initializeNetworkConfigModule( + deployedContracts: Record +) { + // Deploy FeeLevelJudgeStub + const feeLevelJudgeStubAddress = await deployFeeLevelJudgeStub(); + + // Initialize NetworkConfig + await initializeNetworkConfig({ + networkConfig: deployedContracts.networkConfig, + feeLevelJudgeStub: feeLevelJudgeStubAddress + }); + + return { + feeLevelJudgeStub: feeLevelJudgeStubAddress + }; +} + +// Deploys and verifies the contracts. +export default async function () { + // Deploy FlatPriceSaleFactoryV4 contracts + const deployedFlatPriceSaleFactoryV4Module = + await deployFlatPriceSaleFactoryV4Module(); + console.log( + "Deployed FlatPriceSaleFactoryV4Module contracts:", + deployedFlatPriceSaleFactoryV4Module + ); + + // Initialize NetworkConfigModule + const initializedNetworkConfigModule = await initializeNetworkConfigModule( + deployedFlatPriceSaleFactoryV4Module + ); + console.log( + "Initialized NetworkConfigModule contracts:", + initializedNetworkConfigModule + ); +} diff --git a/zksync-ts/deploy/erc20/deploy.ts b/zksync-ts/deploy/erc20/deploy.ts new file mode 100644 index 00000000..f069ead1 --- /dev/null +++ b/zksync-ts/deploy/erc20/deploy.ts @@ -0,0 +1,10 @@ +import { deployContract } from "../utils"; + +// This script is used to deploy an ERC20 token contract +// as well as verify it on Block Explorer if possible for the network + +// Important: make sure to change contract name and symbol in contract source +// at contracts/erc20/MyERC20Token.sol +export default async function () { + await deployContract("MyERC20Token"); +} diff --git a/zksync-ts/deploy/interact.ts b/zksync-ts/deploy/interact.ts new file mode 100644 index 00000000..67f514e3 --- /dev/null +++ b/zksync-ts/deploy/interact.ts @@ -0,0 +1,37 @@ +import * as hre from "hardhat"; +import { getWallet } from "./utils"; +import { ethers } from "ethers"; + +// Address of the contract to interact with +const CONTRACT_ADDRESS = ""; +if (!CONTRACT_ADDRESS) + throw "⛔️ Provide address of the contract to interact with!"; + +// An example of a script to interact with the contract +export default async function () { + console.log(`Running script to interact with contract ${CONTRACT_ADDRESS}`); + + // Load compiled contract info + const contractArtifact = await hre.artifacts.readArtifact("Greeter"); + + // Initialize contract instance for interaction + const contract = new ethers.Contract( + CONTRACT_ADDRESS, + contractArtifact.abi, + getWallet() // Interact with the contract on behalf of this wallet + ); + + // Run contract read function + const response = await contract.greet(); + console.log(`Current message is: ${response}`); + + // Run contract write function + const transaction = await contract.setGreeting("Hello people!"); + console.log(`Transaction hash of setting new message: ${transaction.hash}`); + + // Wait until transaction is processed + await transaction.wait(); + + // Read message after transaction + console.log(`The message now is: ${await contract.greet()}`); +} diff --git a/zksync-ts/deploy/interact_DebutOracle.ts b/zksync-ts/deploy/interact_DebutOracle.ts new file mode 100644 index 00000000..ed8f5c10 --- /dev/null +++ b/zksync-ts/deploy/interact_DebutOracle.ts @@ -0,0 +1,74 @@ +import * as hre from "hardhat"; +import { getWallet } from "./utils"; +import { ethers } from "ethers"; + +// UPDATE VARIABLES +const deployer = getWallet().address; +const saleAddress = "0x453a6bfba54bfcac121f00ab196fc579bb22ba18"; + +// An example of a script to interact with the contract +export default async function () { + const FlatPriceSale_v_4_0Artifact = await hre.artifacts.readArtifact( + "FlatPriceSale_v_4_0" + ); + const FlatPriceSale_v_4_0Contract = new ethers.Contract( + saleAddress, + FlatPriceSale_v_4_0Artifact.abi, + getWallet() + ); + + // Get the current block timestamp + const latestBlock = await hre.ethers.provider.getBlock("latest"); + const currentTimestamp = latestBlock.timestamp; + console.log(`Current block timestamp: ${currentTimestamp}`); + + // Read nativeTokenPriceOracle + // const nativeTokenPriceOracle = + // await FlatPriceSale_v_4_0Contract.nativeTokenPriceOracle(); + // console.log(`Native Token Price Oracle: ${nativeTokenPriceOracle}`); + const nonNativeTokenPriceOracle = + "0x1824D297C6d6D311A204495277B63e943C2D376E"; + + // Read nativeTokenPriceOracleHeartbeat + // const nativeTokenPriceOracleHeartbeat = + // await FlatPriceSale_v_4_0Contract.nativeTokenPriceOracleHeartbeat(); + // console.log( + // `Native Token Price Oracle Heartbeat: ${nativeTokenPriceOracleHeartbeat}` + // ); + const nonNativeTokenPriceOracleHeartbeat = 360000; + + // Call getOraclePrice function + try { + const oraclePrice = await FlatPriceSale_v_4_0Contract.getOraclePrice( + nonNativeTokenPriceOracle, + nonNativeTokenPriceOracleHeartbeat + ); + console.log(`Oracle Price: ${oraclePrice}`); + } catch (error) { + console.error("Error calling getOraclePrice:", error); + } + + // // Load compiled contract info + // const contractArtifact = await hre.artifacts.readArtifact("Greeter"); + + // // Initialize contract instance for interaction + // const contract = new ethers.Contract( + // CONTRACT_ADDRESS, + // contractArtifact.abi, + // getWallet() // Interact with the contract on behalf of this wallet + // ); + + // // Run contract read function + // const response = await contract.greet(); + // console.log(`Current message is: ${response}`); + + // // Run contract write function + // const transaction = await contract.setGreeting("Hello people!"); + // console.log(`Transaction hash of setting new message: ${transaction.hash}`); + + // // Wait until transaction is processed + // await transaction.wait(); + + // // Read message after transaction + // console.log(`The message now is: ${await contract.greet()}`); +} diff --git a/zksync-ts/deploy/interact_NewSale.ts b/zksync-ts/deploy/interact_NewSale.ts new file mode 100644 index 00000000..248d154f --- /dev/null +++ b/zksync-ts/deploy/interact_NewSale.ts @@ -0,0 +1,198 @@ +import * as hre from "hardhat"; +import { getWallet } from "./utils"; +import { ethers } from "ethers"; + +// UPDATE VARIABLES +const deployer = getWallet().address; +const currentNetwork = "zkSync"; +const currentFPSFVersion: string = "v4"; +const currentConfigVersion: keyof typeof configsDatabase = "v1"; +const currentArgsVersion: keyof typeof argsDatabase = "v4"; + +const addressesDatabase = { + mainnet: { + ethOracleAddress: "0x6090149792dAAeE9D1D568c9f9a6F6B46AA29eFD", + usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + usdcOracleAddress: "0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6", + flatPriceSaleFactoryAddress: {} + }, + sepolia: { + ethOracleAddress: "0x694AA1769357215DE4FAC081bf1f309aDC325306", + usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", + usdcOracleAddress: "0xA2F78ab2355fe2f984D808B5CeE7FD0A93D5270E", + flatPriceSaleFactoryAddress: { + v2: "", + "v2.1": "0xaffd5144511fdb139e06733a7413c95a80a9c2ce", + v3: "", + v4: "" + } + }, + baseSepolia: { + ethOracleAddress: "0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1", + usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", + usdcOracleAddress: "0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165", + flatPriceSaleFactoryAddress: { + v2: "", + "v2.1": "0x934b7D8aAf0ab08cf8dbc45839C867038BC73487", + v3: "", + unknown: "0xe085d549c972555b0DD37f01869F235A5Cd0B720", + v4: "0x4Be7467d6011CFB00a4b4cbEF69F03536Ee5f76F" + } + }, + zkSync: { + ethOracleAddress: "0x6D41d1dc818112880b40e26BD6FD347E41008eDA", + usdcAddress: "FILL_ME_IN", + usdcOracleAddress: "FILL_ME_IN", + flatPriceSaleFactoryAddress: { + v2: "FILL_ME_IN", + "v2.1": "FILL_ME_IN", + v3: "FILL_ME_IN", + unknown: "FILL_ME_IN", + v4: "0xd14FE6459168bEFACDe8d9ee9305d9827Ab51709" + } + }, + zkSyncSepolia: { + ethOracleAddress: "0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1", + usdcAddress: "FILL_ME_IN", + usdcOracleAddress: "FILL_ME_IN", + flatPriceSaleFactoryAddress: { + v2: "FILL_ME_IN", + "v2.1": "FILL_ME_IN", + v3: "FILL_ME_IN", + unknown: "FILL_ME_IN", + v4: "0x8465C8ca6d1b8EDF7444d5E4BB9b45e1F35b1D02" + } + } +}; + +const configsDatabase = { + v1: { + // recipient of sale proceeds + recipient: deployer, + // merkle root determining sale access + merkleRoot: + "0x38e08c198f919ebfa9a16d4e54821b360241e6a43aac9e9845dfcd3f85153433", + // sale maximum ($1,000,000) - note the 8 decimal precision! + saleMaximum: 10000000000, + // user maximum ($1,000) + userMaximum: 2000000000, + // purchase minimum ($1) + purchaseMinimum: 1000000000, + // start time: now + startTime: Math.floor(new Date().getTime() / 1000), + // end time (10 days from now) + endTime: Math.floor( + new Date(new Date().getTime() + 10 * 24 * 3600 * 1000).getTime() / 1000 + ), + // max fair queue time 1 hour + maxQueueTime: 0, + // information about the sale + URI: "ipfs://QmZPtoVLFC8HBzEcJ6hoUsXuaQGF5bdQPr7LeQcSFkNMbA" + } +}; + +const argsDatabase = { + v2: [ + // the owner of the new sale (can later modify the sale) + deployer, + // the sale configuration + configsDatabase[currentConfigVersion], + // base currency + "USD", + // native payments enabled + true, + // native price oracle + addressesDatabase[currentNetwork].ethOracleAddress, + // payment tokens + [addressesDatabase[currentNetwork].usdcAddress], + // payment token price oracles + [addressesDatabase[currentNetwork].usdcOracleAddress], + // payment token decimals + [6] + ], + v3: [ + // the owner of the new sale (can later modify the sale) + deployer, + // the sale configuration + configsDatabase[currentConfigVersion], + // base currency + "USD", + // native payments enabled + true, + // native price oracle + addressesDatabase[currentNetwork].ethOracleAddress, + // native price oracle heartbeat + 36000, + // payment tokens + [addressesDatabase[currentNetwork].usdcAddress], + // payment token price oracles + [addressesDatabase[currentNetwork].usdcOracleAddress], + // payment token price oracle heartbeats + [36000], + // payment token decimals + [6] + ], + v4: [ + // the owner of the new sale (can later modify the sale) + deployer, + // the sale configuration + configsDatabase[currentConfigVersion], + // base currency + "USD", + // native payments enabled + true, + // native price oracle + addressesDatabase[currentNetwork].ethOracleAddress, + // native price oracle heartbeat + 36000, + // payment tokens + [], + // payment token price oracles + [], + // payment token price oracle heartbeats + [], + // payment token decimals + [] + ] +}; + +// An example of a script to interact with the contract +export default async function () { + const FlatPriceSaleFactory_v_4_0Artifact = await hre.artifacts.readArtifact( + "FlatPriceSaleFactory_v_4_0" + ); + const FlatPriceSaleFactory_v_4_0 = new ethers.Contract( + addressesDatabase[currentNetwork].flatPriceSaleFactoryAddress.v4, + FlatPriceSaleFactory_v_4_0Artifact.abi, + getWallet() + ); + + const transaction = await FlatPriceSaleFactory_v_4_0.newSale( + ...argsDatabase[currentArgsVersion] + ); + console.log(`Transaction hash of setting new message: ${transaction.hash}`); + + // // Load compiled contract info + // const contractArtifact = await hre.artifacts.readArtifact("Greeter"); + + // // Initialize contract instance for interaction + // const contract = new ethers.Contract( + // CONTRACT_ADDRESS, + // contractArtifact.abi, + // getWallet() // Interact with the contract on behalf of this wallet + // ); + + // // Run contract read function + // const response = await contract.greet(); + // console.log(`Current message is: ${response}`); + + // // Run contract write function + // const transaction = await contract.setGreeting("Hello people!"); + // console.log(`Transaction hash of setting new message: ${transaction.hash}`); + + // // Wait until transaction is processed + // await transaction.wait(); + + // // Read message after transaction + // console.log(`The message now is: ${await contract.greet()}`); +} diff --git a/zksync-ts/deploy/nft/deploy.ts b/zksync-ts/deploy/nft/deploy.ts new file mode 100644 index 00000000..7b81c12b --- /dev/null +++ b/zksync-ts/deploy/nft/deploy.ts @@ -0,0 +1,10 @@ +import { deployContract } from "../utils"; + +// This script is used to deploy an NFT contract +// as well as verify it on Block Explorer if possible for the network +export default async function () { + const name = "My new NFT"; + const symbol = "MYNFT"; + const baseTokenURI = "https://mybaseuri.com/token/"; + await deployContract("MyNFT", [name, symbol, baseTokenURI]); +} diff --git a/zksync-ts/deploy/utils.ts b/zksync-ts/deploy/utils.ts new file mode 100644 index 00000000..f655b75b --- /dev/null +++ b/zksync-ts/deploy/utils.ts @@ -0,0 +1,202 @@ +import { Provider, Wallet } from "zksync-ethers"; +import * as hre from "hardhat"; +import { Deployer } from "@matterlabs/hardhat-zksync"; +import dotenv from "dotenv"; +import { ethers } from "ethers"; + +import "@matterlabs/hardhat-zksync-node/dist/type-extensions"; +import "@matterlabs/hardhat-zksync-verify/dist/src/type-extensions"; + +// Load env file +dotenv.config(); + +export const getProvider = () => { + const rpcUrl = hre.network.config.url; + if (!rpcUrl) + throw `⛔️ RPC URL wasn't found in "${hre.network.name}"! Please add a "url" field to the network config in hardhat.config.ts`; + + // Initialize ZKsync Provider + const provider = new Provider(rpcUrl); + + return provider; +}; + +export const getWallet = (privateKey?: string) => { + if (!privateKey) { + // Get wallet private key from .env file + if (!process.env.WALLET_PRIVATE_KEY) + throw "⛔️ Wallet private key wasn't found in .env file!"; + } + + const provider = getProvider(); + + // Initialize ZKsync Wallet + const wallet = new Wallet( + privateKey ?? process.env.WALLET_PRIVATE_KEY!, + provider + ); + + return wallet; +}; + +export const verifyEnoughBalance = async (wallet: Wallet, amount: bigint) => { + // Check if the wallet has enough balance + const balance = await wallet.getBalance(); + if (balance < amount) + throw `⛔️ Wallet balance is too low! Required ${ethers.formatEther( + amount + )} ETH, but current ${wallet.address} balance is ${ethers.formatEther( + balance + )} ETH`; +}; + +/** + * @param {string} data.contract The contract's path and name. E.g., "contracts/Greeter.sol:Greeter" + */ +export const verifyContract = async (data: { + address: string; + contract: string; + constructorArguments: string; + bytecode: string; +}) => { + const verificationRequestId: number = await hre.run("verify:verify", { + ...data, + noCompile: true + }); + return verificationRequestId; +}; + +type DeployContractOptions = { + /** + * If true, the deployment process will not print any logs + */ + silent?: boolean; + /** + * If true, the contract will not be verified on Block Explorer + */ + noVerify?: boolean; + /** + * If specified, the contract will be deployed using this wallet + */ + wallet?: Wallet; +}; +export const deployContract = async ( + contractArtifactName: string, + constructorArguments?: any[], + options?: DeployContractOptions +) => { + const log = (message: string) => { + if (!options?.silent) console.log(message); + }; + + log(`\nStarting deployment process of "${contractArtifactName}"...`); + + const wallet = options?.wallet ?? getWallet(); + const deployer = new Deployer(hre, wallet); + console.log(`[ab] we get to before the artifact`); + const artifact = await deployer + .loadArtifact(contractArtifactName) + .catch((error) => { + if ( + error?.message?.includes( + `Artifact for contract "${contractArtifactName}" not found.` + ) + ) { + console.error(error.message); + throw `⛔️ Please make sure you have compiled your contracts or specified the correct contract name!`; + } else { + throw error; + } + }); + + // Estimate contract deployment fee + const deploymentFee = await deployer.estimateDeployFee( + artifact, + constructorArguments || [] + ); + log(`Estimated deployment cost: ${ethers.formatEther(deploymentFee)} ETH`); + + // Check if the wallet has enough balance + await verifyEnoughBalance(wallet, deploymentFee); + + // Deploy the contract to ZKsync + const contract = await deployer.deploy(artifact, constructorArguments); + const address = await contract.getAddress(); + const constructorArgs = contract.interface.encodeDeploy(constructorArguments); + const fullContractSource = `${artifact.sourceName}:${artifact.contractName}`; + + // Display contract deployment info + log(`\n"${artifact.contractName}" was successfully deployed:`); + log(` - Contract address: ${address}`); + log(` - Contract source: ${fullContractSource}`); + log(` - Encoded constructor arguments: ${constructorArgs}\n`); + + if (!options?.noVerify && hre.network.config.verifyURL) { + log(`Requesting contract verification...`); + await verifyContract({ + address, + contract: fullContractSource, + constructorArguments: constructorArgs, + bytecode: artifact.bytecode + }); + } + + return contract; +}; + +/** + * Rich wallets can be used for testing purposes. + * Available on ZKsync In-memory node and Dockerized node. + */ +export const LOCAL_RICH_WALLETS = [ + { + address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", + privateKey: + "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110" + }, + { + address: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", + privateKey: + "0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3" + }, + { + address: "0x0D43eB5B8a47bA8900d84AA36656c92024e9772e", + privateKey: + "0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e" + }, + { + address: "0xA13c10C0D5bd6f79041B9835c63f91de35A15883", + privateKey: + "0x850683b40d4a740aa6e745f889a6fdc8327be76e122f5aba645a5b02d0248db8" + }, + { + address: "0x8002cD98Cfb563492A6fB3E7C8243b7B9Ad4cc92", + privateKey: + "0xf12e28c0eb1ef4ff90478f6805b68d63737b7f33abfa091601140805da450d93" + }, + { + address: "0x4F9133D1d3F50011A6859807C837bdCB31Aaab13", + privateKey: + "0xe667e57a9b8aaa6709e51ff7d093f1c5b73b63f9987e4ab4aa9a5c699e024ee8" + }, + { + address: "0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA", + privateKey: + "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959" + }, + { + address: "0xedB6F5B4aab3dD95C7806Af42881FF12BE7e9daa", + privateKey: + "0x74d8b3a188f7260f67698eb44da07397a298df5427df681ef68c45b34b61f998" + }, + { + address: "0xe706e60ab5Dc512C36A4646D719b889F398cbBcB", + privateKey: + "0xbe79721778b48bcc679b78edac0ce48306a8578186ffcb9f2ee455ae6efeace1" + }, + { + address: "0xE90E12261CCb0F3F7976Ae611A29e84a6A85f424", + privateKey: + "0x3eb15da85647edd9a1159a4a13b9e7c56877c4eb33f614546d4db06a51868b1c" + } +]; diff --git a/zksync-ts/deployments-zk/zkSyncMainnet/.chainId b/zksync-ts/deployments-zk/zkSyncMainnet/.chainId new file mode 100644 index 00000000..6f7d0b04 --- /dev/null +++ b/zksync-ts/deployments-zk/zkSyncMainnet/.chainId @@ -0,0 +1 @@ +0x144 \ No newline at end of file diff --git a/zksync-ts/deployments-zk/zkSyncMainnet/contracts/claim/FeeLevelJudgeStub.sol/FeeLevelJudgeStub.json b/zksync-ts/deployments-zk/zkSyncMainnet/contracts/claim/FeeLevelJudgeStub.sol/FeeLevelJudgeStub.json new file mode 100644 index 00000000..c8491398 --- /dev/null +++ b/zksync-ts/deployments-zk/zkSyncMainnet/contracts/claim/FeeLevelJudgeStub.sol/FeeLevelJudgeStub.json @@ -0,0 +1,49 @@ +{ + "sourceName": "contracts/claim/FeeLevelJudgeStub.sol", + "contractName": "FeeLevelJudgeStub", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_feeLevel", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "getFeeLevel", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x000000600310027000000012033001970000000100200190000000190000c13d0000008002000039000000400020043f000000040030008c000000390000413d000000000201043b0000001602200197000000170020009c000000390000c13d000000240030008c000000390000413d0000000002000416000000000002004b000000390000c13d0000000401100370000000000101043b000000180010009c000000390000213d000000000100041a000000800010043f0000001901000041000000430001042e0000000002000416000000000002004b000000390000c13d0000001f0230003900000013022001970000008002200039000000400020043f0000001f0430018f000000140530019800000080025000390000002a0000613d0000008006000039000000000701034f000000007807043c0000000006860436000000000026004b000000260000c13d000000000004004b000000370000613d000000000151034f0000000304400210000000000502043300000000054501cf000000000545022f000000000101043b0000010004400089000000000141022f00000000014101cf000000000151019f0000000000120435000000200030008c0000003b0000813d00000000010000190000004400010430000000800100043d000000000010041b0000002001000039000001000010044300000120000004430000001501000041000000430001042e0000004200000432000000430001042e000000440001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000001ffffffe000000000000000000000000000000000000000000000000000000000ffffffe00000000200000000000000000000000000000040000001000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000b31c878100000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000020000000800000000000000000a9535a0b118c36ffd409886b497bf32212916d1241d3b06aab028a13e8c18e17", + "entries": [ + { + "constructorArgs": [ + 100 + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [], + "address": "0x73ca56112B35AD85A7ec55bAAfA542Ef25889858", + "txHash": "0xa4449b3efd9edcf337c3a3d509912af018a97d48f4aa528fec9024946a61e948" + } + ] +} diff --git a/zksync-ts/deployments-zk/zkSyncMainnet/contracts/config/NetworkConfig.sol/NetworkConfig.json b/zksync-ts/deployments-zk/zkSyncMainnet/contracts/config/NetworkConfig.sol/NetworkConfig.json new file mode 100644 index 00000000..8e4ea597 --- /dev/null +++ b/zksync-ts/deployments-zk/zkSyncMainnet/contracts/config/NetworkConfig.sol/NetworkConfig.json @@ -0,0 +1,245 @@ +{ + "sourceName": "contracts/config/NetworkConfig.sol", + "contractName": "NetworkConfig", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "accessAuthorityAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeRecipient", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAccessAuthorityAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getFeeRecipient", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNativeTokenPriceOracleAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNativeTokenPriceOracleHeartbeat", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStakingAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "_feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "_stakingAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_nativeTokenPriceOracleAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nativeTokenPriceOracleHeartbeat", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_accessAuthorityAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "nativeTokenPriceOracleAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeTokenPriceOracleHeartbeat", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakingAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x0001000000000002000900000000000200000000000103550000008003000039000000400030043f0000000100200190000000680000c13d00000060021002700000006303200197000000040030008c000000d40000413d000000000201043b000000e002200270000000650020009c000000700000213d0000006f0020009c000000790000a13d000000700020009c0000008f0000213d000000730020009c0000007f0000613d000000740020009c000000d40000c13d000000a40030008c000000d40000413d0000000002000416000000000002004b000000d40000c13d0000000402100370000000000402043b000000780040009c000000d40000213d0000002402100370000000000502043b000000780050009c000000d40000213d0000004402100370000000000602043b000000780060009c000000d40000213d0000008401100370000000000701043b000000780070009c000000d40000213d0000000003000415000000090330008a0000000503300210000000000200041a0000ff0001200190000000f60000c13d0000000003000415000000080330008a0000000503300210000000ff00200190000000f60000c13d000000880120019700000101011001bf0000000008000019000000000010041b0000006602000039000000000302041a00000089003001980000012a0000c13d000700000008001d00000078044001970000007805500197000000780660019700000078077001970000006508000039000000000908041a0000008009900197000000000449019f000000000048041b0000008c03300197000000000335019f0000008d033001c7000000000032041b0000006702000039000000000302041a0000008003300197000000000363019f000000000032041b00000064020000390000000002200367000000000202043b0000006803000039000000000023041b0000006902000039000000000302041a0000008003300197000000000373019f000000000032041b0000ff00001001900000013e0000c13d000000400100043d00000064021000390000009003000041000000000032043500000044021000390000009103000041000000000032043500000024021000390000002b03000039000001330000013d0000000001000416000000000001004b000000d40000c13d0000002001000039000001000010044300000120000004430000006401000041000001890001042e000000660020009c000000840000a13d000000670020009c000000ac0000213d0000006a0020009c000000b80000613d0000006b0020009c000000bd0000613d000000d40000013d000000750020009c000000b80000613d000000760020009c0000008a0000613d000000770020009c000000d40000c13d0000000001000416000000000001004b000000d40000c13d0000006501000039000000c60000013d0000006c0020009c000000c20000613d0000006d0020009c000000b00000613d0000006e0020009c000000d40000c13d0000000001000416000000000001004b000000d40000c13d0000006701000039000000c60000013d000000710020009c000000bd0000613d000000720020009c000000d40000c13d0000000001000416000000000001004b000000d40000c13d0000003301000039000000000201041a00000078052001970000000003000411000000000035004b000000ea0000c13d0000008002200197000000000021041b0000000001000414000000630010009c0000006301008041000000c00110021000000081011001c70000800d020000390000000303000039000000820400004100000000060000190188017e0000040f0000000100200190000000d40000613d0000000001000019000001890001042e000000680020009c000000cb0000613d000000690020009c000000d40000c13d0000000001000416000000000001004b000000d40000c13d0000006801000039000000000101041a000000800010043f0000007d01000041000001890001042e0000000001000416000000000001004b000000d40000c13d0000006601000039000000c60000013d0000000001000416000000000001004b000000d40000c13d0000006901000039000000c60000013d0000000001000416000000000001004b000000d40000c13d0000003301000039000000000101041a0000007801100197000000800010043f0000007d01000041000001890001042e000000240030008c000000d40000413d0000000002000416000000000002004b000000d40000c13d0000000401100370000000000101043b000000780010009c000000d60000a13d00000000010000190000018a000104300000003302000039000000000202041a00000078022001970000000003000411000000000032004b000000ea0000c13d0000007800100198000000f30000c13d0000007901000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000007a01000041000000c40010043f0000007b01000041000000e40010043f0000007c010000410000018a000104300000007901000041000000800010043f0000002001000039000000840010043f000000a40010043f0000007e01000041000000c40010043f0000007f010000410000018a00010430018801680000040f0000000001000019000001890001042e000700000003001d000100000001001d000600000002001d000200000007001d000300000006001d000400000005001d000500000004001d00000083010000410000000000100443000000000100041000000004001004430000000001000414000000630010009c0000006301008041000000c00110021000000084011001c70000800202000039018801830000040f00000001002001900000011f0000613d000000000101043b000000000001004b000001200000c13d0000000602000029000000ff0120018f000000010010008c00000007010000290000000501100270000000000100003f000000010100603f000001230000c13d000000010000006b0000000504000029000000040500002900000003060000290000000207000029000000370000613d0000009201200197000000010800003900000001011001bf0000003a0000013d000000000001042f00000007010000290000000501100270000000000100003f000000400100043d00000064021000390000008503000041000000000032043500000044021000390000008603000041000001300000013d000000400100043d00000064021000390000008a03000041000000000032043500000044021000390000008b03000041000000000032043500000024021000390000002e03000039000000000032043500000079020000410000000000210435000000040210003900000020030000390000000000320435000000630010009c0000006301008041000000400110021000000087011001c70000018a00010430000000000100041100000078061001970000003301000039000000000201041a0000008003200197000000000363019f000000000031041b00000000010004140000007805200197000000630010009c0000006301008041000000c00110021000000081011001c70000800d02000039000000030300003900000082040000410188017e0000040f0000000100200190000000d40000613d000000070000006b000000aa0000c13d000000000200041a0000009301200197000000000010041b0000000103000039000000400100043d0000000000310435000000630010009c000000630100804100000040011002100000000002000414000000630020009c0000006302008041000000c002200210000000000112019f0000008e011001c70000800d020000390000008f040000410188017e0000040f0000000100200190000000aa0000c13d000000d40000013d00000078061001970000003301000039000000000201041a0000008003200197000000000363019f000000000031041b00000000010004140000007805200197000000630010009c0000006301008041000000c00110021000000081011001c70000800d02000039000000030300003900000082040000410188017e0000040f00000001002001900000017b0000613d000000000001042d00000000010000190000018a00010430000000000001042f00000181002104210000000102000039000000000001042d0000000002000019000000000001042d00000186002104230000000102000039000000000001042d0000000002000019000000000001042d0000018800000432000001890001042e0000018a00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000d7b4be2300000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000f5b1a3ac00000000000000000000000000000000000000000000000000000000d7b4be2400000000000000000000000000000000000000000000000000000000e0b626d7000000000000000000000000000000000000000000000000000000008da5cb5b000000000000000000000000000000000000000000000000000000009d326b3d00000000000000000000000000000000000000000000000000000000bbcda115000000000000000000000000000000000000000000000000000000004ccb20bf000000000000000000000000000000000000000000000000000000006afb5cec000000000000000000000000000000000000000000000000000000006afb5ced00000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000004ccb20c000000000000000000000000000000000000000000000000000000000530b97a4000000000000000000000000000000000000000000000000000000000e9ed68b00000000000000000000000000000000000000000000000000000000163f21bd0000000000000000000000000000000000000000000000000000000046904840000000000000000000000000ffffffffffffffffffffffffffffffffffffffff08c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000080000000000000000000000000000000000000000000000000000000200000008000000000000000004f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65720000000000000000000000000000000000000064000000800000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e01806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c7265610000000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000ff0000000000000000000000000000000000000000656e20696e697469616c697a6564000000000000000000000000000000000000436f6e747261637420696e7374616e63652068617320616c7265616479206265ffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000002000000000000000000000000000000000000200000000000000000000000007f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024986e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f742069ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffe3eb485ef07ba86a5e060b9f1b271cafe6036f12a16dfc3be0de6e2f8f6de418", + "entries": [ + { + "constructorArgs": [], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [], + "address": "0xEd852E0b2B936E95dcbE43244d3F55525A79d4F7", + "txHash": "0x0720e72c341e8fc94562660bb69d1f52c754e42c03833b8ff88e516788c0303c" + } + ] +} diff --git a/zksync-ts/deployments-zk/zkSyncMainnet/contracts/sale/v4/FlatPriceSale.sol/FlatPriceSale_v_4_0.json b/zksync-ts/deployments-zk/zkSyncMainnet/contracts/sale/v4/FlatPriceSale.sol/FlatPriceSale_v_4_0.json new file mode 100644 index 00000000..e0efa844 --- /dev/null +++ b/zksync-ts/deployments-zk/zkSyncMainnet/contracts/sale/v4/FlatPriceSale.sol/FlatPriceSale_v_4_0.json @@ -0,0 +1,1032 @@ +{ + "sourceName": "contracts/sale/v4/FlatPriceSale.sol", + "contractName": "FlatPriceSale_v_4_0", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_networkConfig", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "baseCurrencyValue", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenValue", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "protocolTokenFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "platformTokenFee", + "type": "uint256" + } + ], + "name": "Buy", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract INetworkConfig", + "name": "networkConfig", + "type": "address" + } + ], + "name": "ImplementationConstructor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "indexed": false, + "internalType": "struct Config", + "name": "config", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "string", + "name": "baseCurrency", + "type": "string" + }, + { + "indexed": false, + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "nativeOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "nativePaymentsEnabled", + "type": "bool" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "distributor", + "type": "address" + } + ], + "name": "RegisterDistributor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + } + ], + "indexed": false, + "internalType": "struct PaymentTokenInfo", + "name": "paymentTokenInfo", + "type": "tuple" + } + ], + "name": "SetPaymentTokenInfo", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SweepNative", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SweepToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "indexed": false, + "internalType": "struct Config", + "name": "config", + "type": "tuple" + } + ], + "name": "Update", + "type": "event" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseCurrency", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "userLimit", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "expiresAt", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "address payable", + "name": "platformFlatRateFeeRecipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "platformFlatRateFeeAmount", + "type": "uint256" + } + ], + "name": "buyWithNative", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "quantity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userLimit", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "expiresAt", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "address payable", + "name": "platformFlatRateFeeRecipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "platformFlatRateFeeAmount", + "type": "uint256" + } + ], + "name": "buyWithToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "buyerTotal", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "config", + "outputs": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "generatePseudorandomValue", + "outputs": [ + { + "internalType": "uint160", + "name": "", + "type": "uint160" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "buyer", + "type": "address" + } + ], + "name": "getFairQueueTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + } + ], + "name": "getOraclePrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + } + ], + "name": "getPaymentToken", + "outputs": [ + { + "components": [ + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + } + ], + "internalType": "struct PaymentTokenInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "internalType": "struct Config", + "name": "_config", + "type": "tuple" + }, + { + "internalType": "string", + "name": "_baseCurrency", + "type": "string" + }, + { + "internalType": "bool", + "name": "_nativePaymentsEnabled", + "type": "bool" + }, + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "_nativeTokenPriceOracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nativeTokenPriceOracleHeartbeat", + "type": "uint256" + }, + { + "internalType": "contract IERC20Upgradeable[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck[]", + "name": "oracles", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "oracleHeartbeats", + "type": "uint256[]" + }, + { + "internalType": "uint8[]", + "name": "decimals", + "type": "uint8[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isOpen", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isOver", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "metrics", + "outputs": [ + { + "internalType": "uint256", + "name": "purchaseCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "buyerCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseTotal", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeTokenPriceOracle", + "outputs": [ + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeTokenPriceOracleHeartbeat", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "networkConfig", + "outputs": [ + { + "internalType": "contract INetworkConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "", + "type": "address" + } + ], + "name": "paymentTokens", + "outputs": [ + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dest", + "type": "address" + } + ], + "name": "payments", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_distributor", + "type": "address" + } + ], + "name": "registerDistributor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sweepNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenQuantity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tokenDecimals", + "type": "uint256" + }, + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + } + ], + "name": "tokensToBaseCurrency", + "outputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "total", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "internalType": "struct Config", + "name": "_config", + "type": "tuple" + } + ], + "name": "update", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "payee", + "type": "address" + } + ], + "name": "withdrawPayments", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x000400000000000200130000000000020000006004100270000005ad0340019700030000003103550002000000010355000005ad0040019d0000000100200190000000f00000c13d000000e002000039000000400020043f000000040030008c000001130000413d000000c00000043f000000000201043b000000e002200270000005b90020009c000001150000a13d000005ba0020009c000001860000a13d000005bb0020009c000001990000213d000005c10020009c000002b10000213d000005c40020009c000004830000613d000005c50020009c000001130000c13d000001440030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000402100370000000000202043b000005b00020009c000001130000213d000000800020043f0000002402100370000000000202043b000005ed0020009c000001130000213d0000000002230049000005ee0020009c000001130000213d000001240020008c000001130000413d0000004402100370000000000202043b000005ed0020009c000001130000213d0000002304200039000000000034004b000001130000813d0000000404200039000000000441034f000000000404043b000005ed0040009c000001130000213d00000000024200190000002402200039000000000032004b000001130000213d0000006402100370000000000402043b000000000004004b0000000002000039000000010200c039000f00000004001d000000000024004b000001130000c13d0000008402100370000000000202043b000e00000002001d000005b00020009c000001130000213d000000c402100370000000000202043b000005ed0020009c000001130000213d0000002304200039000000000034004b000001130000813d0000000404200039000000000441034f000000000404043b000d00000004001d000005ed0040009c000001130000213d000c00240020003d0000000d0200002900000005022002100000000c02200029000000000032004b000001130000213d000000e402100370000000000202043b000005ed0020009c000001130000213d0000002304200039000000000034004b000001130000813d0000000404200039000000000441034f000000000404043b000b00000004001d000005ed0040009c000001130000213d000a00240020003d0000000b0200002900000005022002100000000a02200029000000000032004b000001130000213d0000010402100370000000000202043b000005ed0020009c000001130000213d0000002304200039000000000034004b000001130000813d0000000404200039000000000441034f000000000404043b000900000004001d000005ed0040009c000001130000213d000800240020003d000000090200002900000005022002100000000802200029000000000032004b000001130000213d0000012402100370000000000202043b000005ed0020009c000001130000213d0000002304200039000000000034004b000001130000813d0000000404200039000000000141034f000000000101043b000700000001001d000005ed0010009c000001130000213d000500240020003d000000070100002900000005011002100000000501100029000000000031004b000001130000213d000000000100041a000600000001001d0000ff000010019000000000010000390000000101006039000000a00010043f000009e10000c13d0000000001000415000000100110008a00040005001002180000000601000029000000ff00100190001000000000003d001000010000603d0000000001000019000009e50000c13d0000000602000029000005f30220019700000101022001bf000000000021041b000000400100043d000005f40010009c0000076a0000213d0000012002100039000000400020043f000000d002000039000000000202041a000005b0022001970000000002210436000000d103000039000000000303041a0000000000320435000000d202000039000000000202041a0000004003100039000400000003001d0000000000230435000000d302000039000000000202041a00000060031000390000000000230435000000d402000039000000000202041a00000080031000390000000000230435000000d502000039000000000202041a000000a003100039000600000003001d0000000000230435000000d602000039000000000302041a000000c0021000390000000000320435000000e003100039000000d704000039000000000404041a0000000000430435000000d803000039000000000603041a000000010760019000000001046002700000007f0440618f0000001f0040008c00000000030000390000000103002039000000000336013f00000001003001900000047d0000c13d000000400300043d0000000005430436000000000007004b00000b180000613d000000d806000039000000000060043f000000000004004b000000000600001900000b1d0000613d000005f50700004100000000060000190000000008560019000000000907041a000000000098043500000001077000390000002006600039000000000046004b000000e80000413d00000b1d0000013d0000000002000416000000000002004b000001130000c13d0000001f02300039000005ae02200197000000a002200039000000400020043f0000001f0430018f000005af05300198000000a002500039000001010000613d000000a006000039000000000701034f000000007807043c0000000006860436000000000026004b000000fd0000c13d000000000004004b0000010e0000613d000000000151034f0000000304400210000000000502043300000000054501cf000000000545022f000000000101043b0000010004400089000000000141022f00000000014101cf000000000151019f0000000000120435000000200030008c000001130000413d000000a00300043d000005b00030009c000001410000a13d0000000001000019000016b100010430000005cf0020009c000001790000213d000005d90020009c000001b90000a13d000005da0020009c0000020b0000213d000005dd0020009c000002e80000613d000005de0020009c000001130000c13d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000f00000001001d000005b00010009c000001130000213d0000009701000039000000000101041a000005ef020000410000000000200443000005b001100197000e00000001001d00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000000001004b000006690000c13d000000c00100043d000005ad0010009c000005ad01008041000005e4011000d1000016b100010430000000000100041a0000ff0000100190000001a50000c13d000000ff0210018f000000ff0020008c0000015d0000613d000000ff011001bf000000000010041b000000ff01000039000000400200043d0000000000120435000005ad0020009c000005ad0200804100000040012002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f000005b5011001c70000800d02000039000f00000003001d0000000103000039000005b60400004116af16a50000040f0000000f030000290000000100200190000001130000613d000005b001300197000000800010043f000000400200043d0000000000120435000005ad0020009c000005ad0200804100000040012002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f000005b5011001c70000800d020000390000000103000039000005b70400004116af16a50000040f0000000100200190000001130000613d000000800100043d000001400000044300000160001004430000002001000039000001000010044300000001010000390000012000100443000005b801000041000016b00001042e000005d00020009c000001c40000a13d000005d10020009c0000021f0000213d000005d40020009c000003030000613d000005d50020009c000001130000c13d0000000001000416000000000001004b000001130000c13d0000006501000039000001a30000013d000005c60020009c000001f70000a13d000005c70020009c000002340000213d000005ca0020009c000003940000613d000005cb0020009c000001130000c13d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d16af12f00000040f000004680000013d000005bc0020009c000002d00000213d000005bf0020009c000004a00000613d000005c00020009c000001130000c13d0000000001000416000000000001004b000001130000c13d000000cc01000039000000000101041a000002300000013d000000400100043d0000006402100039000005b10300004100000000003204350000004402100039000005b2030000410000000000320435000000240210003900000027030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad010080410000004001100210000005b4011001c7000016b100010430000005df0020009c000004560000613d000005e00020009c000004210000613d000005e10020009c000001130000c13d0000000001000416000000000001004b000001130000c13d000000db01000039000004520000013d000005d60020009c000004640000613d000005d70020009c000004390000613d000005d80020009c000001130000c13d0000000001000416000000000001004b000001130000c13d000000d803000039000000000203041a000000010420019000000001012002700000007f0110618f0000001f0010008c00000000050000390000000105002039000000000054004b0000047d0000c13d000000d705000039000000000c05041a000000d605000039000000000b05041a000000d505000039000000000a05041a000000d405000039000000000905041a000000d305000039000000000805041a000000d205000039000000000705041a000000d105000039000000000605041a000000d005000039000000000505041a000000e00010043f000000000004004b000f00000006001d000e00000007001d000d00000008001d000c00000009001d000b0000000a001d000a0000000b001d00090000000c001d000800000005001d0000055d0000613d000000000030043f000000000001004b000006860000c13d0000010001000039000006910000013d000005cc0020009c0000046f0000613d000005cd0020009c0000044e0000613d000005ce0020009c000001130000c13d0000000001000416000000000001004b000001130000c13d000000db01000039000000000101041a000000da02000039000000000202041a000000d903000039000000000303041a000000e00030043f000001000020043f000001200010043f000005ec01000041000016b00001042e000005db0020009c000003420000613d000005dc0020009c000001130000c13d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d000000000010043f000000dc01000039000000200010043f0000004002000039000000000100001916af16720000040f000004520000013d000005d20020009c000003650000613d000005d30020009c000001130000c13d0000000001000416000000000001004b000001130000c13d0000000001000412001300000001001d001200000000003d000080050100003900000044030000390000000004000415000000130440008a00000005044002100000061c0200004116af16870000040f000005b001100197000000e00010043f000005e801000041000016b00001042e000005c80020009c000004060000613d000005c90020009c000001130000c13d000000e40030008c000001130000413d0000000402100370000000000202043b000f00000002001d000005b00020009c000001130000213d0000004402100370000000000202043b000e00000002001d0000002402100370000000000202043b000c00000002001d0000006402100370000000000202043b000d00000002001d000005ed0020009c000001130000213d0000008402100370000000000402043b000005ed0040009c000001130000213d0000002302400039000000000032004b000001130000813d0000000405400039000000000251034f000000000202043b000005ed0020009c0000076a0000213d0000001f0720003900000650077001970000003f0770003900000650077001970000061a0070009c0000076a0000213d000000e007700039000000400070043f000000e00020043f00000000042400190000002404400039000000000034004b000001130000213d0000002003500039000000000431034f00000650052001980000001f0620018f00000100035000390000026f0000613d0000010007000039000000000804034f000000008908043c0000000007970436000000000037004b0000026b0000c13d000000000006004b0000027c0000613d000000000454034f0000000305600210000000000603043300000000065601cf000000000656022f000000000404043b0000010005500089000000000454022f00000000045401cf000000000464019f000000000043043500000100022000390000000000020435000000a402100370000000000202043b000b00000002001d000005b00020009c000001130000213d000000c401100370000000000101043b000a00000001001d0000061b0100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b0000000002000411000000000012004b000007a30000c13d0000061c0100004100000000001004430000000001000412000000040010044300000024000004430000000001000414000005ad0010009c000005ad01008041000000c0011002100000061d011001c7000080050200003916af16aa0000040f0000000100200190000011b60000613d000000000201043b000000400300043d0000061e01000041000900000003001d00000000001304350000000001000414000005b002200197000800000002001d000000040020008c0000083e0000c13d0000000104000031000000200040008c0000002004008039000008690000013d000005c20020009c000004b60000613d000005c30020009c000001130000c13d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d000000000010043f000000cf01000039000000200010043f0000004002000039000000000100001916af16720000040f0000000202100039000000000202041a0000000103100039000000000303041a000000000101041a000005b001100197000000e00010043f000001000030043f000000ff0120018f000001200010043f000005ec01000041000016b00001042e000005bd0020009c000004d60000613d000005be0020009c000001130000c13d0000000001000416000000000001004b000001130000c13d0000012001000039000000400010043f0000000301000039000000e00010043f000005e201000041000001000010043f0000002001000039000001200010043f000000e001000039000001400200003916af11e50000040f000001200110008a000005ad0010009c000005ad010080410000006001100210000005e3011001c7000016b00001042e000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d0000006502000039000000000202041a000005b0022001970000000003000411000000000032004b000004f30000c13d000000000001004b000006290000c13d000005b301000041000000e00010043f0000002001000039000000e40010043f0000001901000039000001040010043f0000064c01000041000001240010043f0000064901000041000016b100010430000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000402100370000000000202043b000f00000002001d000005ed0020009c000001130000213d0000000f0230006a000e00000002001d000005ee0020009c000001130000213d0000000e02000029000001240020008c000001130000413d0000020002000039000000400020043f000000d003000039000000000303041a000005b003300197000000e00030043f000000d103000039000000000303041a000001000030043f000000d203000039000000000303041a000001200030043f000000d303000039000000000303041a000001400030043f000000d403000039000000000303041a000001600030043f000000d503000039000000000303041a000001800030043f000000d603000039000000000303041a000001a00030043f000000d703000039000000000303041a000001c00030043f000000d807000039000000000407041a000000010540019000000001034002700000007f0330618f0000001f0030008c00000000060000390000000106002039000000000664013f00000001006001900000047d0000c13d000002000030043f000000000005004b000006f40000613d000000000070043f000000000003004b000006fc0000c13d0000002003000039000007090000013d0000000001000416000000000001004b000001130000c13d000000d501000039000000000101041a000f00000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b0000000f0010006b0000000002000019000003630000813d000000d602000039000000000202041a000000000012004b0000000002000019000003630000a13d000000d201000039000000000101041a000000db02000039000000000202041a000000000012004b00000000020000390000000102004039000000010120018f000004680000013d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d0000014002000039000000400020043f000000e00000043f000001000000043f000001200000043f000000000010043f000000cf01000039000000200010043f0000004002000039000000000100001916af16720000040f000f00000001001d000001400100003916af11c80000040f0000000f03000029000000000103041a000005b001100197000001400010043f0000000102300039000000000202041a000001600020043f0000000202300039000000000202041a000000ff0220018f000001800020043f000000400200043d0000000001120436000001600300043d0000000000310435000001800100043d000000ff0110018f00000040032000390000000000130435000005ad0020009c000005ad02008041000000400120021000000632011001c7000016b00001042e000000a40030008c000001130000413d0000000402100370000000000202043b000f00000002001d0000002402100370000000000202043b000e00000002001d000005ed0020009c000001130000213d0000004402100370000000000402043b000005ed0040009c000001130000213d0000002302400039000000000032004b000001130000813d0000000405400039000000000251034f000000000202043b000005ed0020009c0000076a0000213d0000001f0720003900000650077001970000003f0770003900000650077001970000061a0070009c0000076a0000213d000000e007700039000000400070043f000000e00020043f00000000042400190000002404400039000000000034004b000001130000213d0000002003500039000000000431034f00000650052001980000001f0620018f0000010003500039000003c30000613d0000010007000039000000000804034f000000008908043c0000000007970436000000000037004b000003bf0000c13d000000000006004b000003d00000613d000000000454034f0000000305600210000000000603043300000000065601cf000000000656022f000000000404043b0000010005500089000000000454022f00000000045401cf000000000464019f0000000000430435000001000220003900000000000204350000006402100370000000000202043b000d00000002001d000005b00020009c000001130000213d0000008401100370000000000101043b000c00000001001d0000061b0100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b0000000002000411000000000012004b000007a30000c13d0000061c0100004100000000001004430000000001000412000000040010044300000024000004430000000001000414000005ad0010009c000005ad01008041000000c0011002100000061d011001c7000080050200003916af16aa0000040f0000000100200190000011b60000613d000000000201043b000000400300043d0000061e01000041000b00000003001d00000000001304350000000001000414000005b002200197000a00000002001d000000040020008c000007c10000c13d0000000103000031000000200030008c00000020040000390000000004034019000007ec0000013d0000000001000416000000000001004b000001130000c13d00000627010000410000000000100443000000000100041000000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c70000800a0200003916af16aa0000040f0000000100200190000011b60000613d000000c00500043d000000000301043b000000d001000039000000000201041a0000000001000414000005b004200197000000040040008c000005040000c13d00000001020000390000000109000031000005dd0000013d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000f00000001001d000005b00010009c000001130000213d0000064d01000041000000e00010043f0000000001000410000000e40010043f00000000010004140000000f06000029000000040060008c000005220000c13d0000000103000031000000200030008c0000002004000039000000000403401900000000050000190000054a0000013d0000000001000416000000000001004b000001130000c13d0000006501000039000000000201041a000005b0052001970000000003000411000000000035004b000004f30000c13d000005fe02200197000000000021041b0000000001000414000005ad0010009c000005ad01008041000000c0011002100000060a011001c70000800d02000039000000030300003900000614040000410000000006000019000006370000013d0000000001000416000000000001004b000001130000c13d000000cd01000039000000000101041a000000e00010043f000005e801000041000016b00001042e000000440030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000402100370000000000302043b000005b00030009c000001130000213d0000002401100370000000000201043b000000000103001916af120c0000040f000004680000013d0000000001000416000000000001004b000001130000c13d16af12cb0000040f000000400200043d0000000000120435000005ad0020009c000005ad020080410000004001200210000005eb011001c7000016b00001042e0000000001000416000000000001004b000001130000c13d000000cb03000039000000000203041a000000010420019000000001012002700000007f0110618f0000001f0010008c00000000050000390000000105002039000000000552013f0000000100500190000004fc0000613d0000060c01000041000000000010043f0000002201000039000000040010043f0000060d01000041000016b1000104300000000001000416000000000001004b000001130000c13d000000d601000039000000000101041a000f00000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b0000000f0010006b0000055a0000a13d000000d201000039000000000101041a000000db02000039000000000202041a000000000012004b00000000010000390000000101008039000000010110018f000004680000013d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d0000009702000039000000000202041a000005e903000041000000e00030043f000000e40010043f0000000001000414000005b002200197000000040020008c000005630000c13d0000000104000031000000200040008c0000002004008039000005880000013d000000840030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000004402100370000000000302043b000005b00030009c000001130000213d0000006401100370000000000201043b000000000103001916af120c0000040f00000002030003670000000402300370000000000402043b00000000021400a9000000000004004b000004cc0000613d00000000044200d9000000000014004b000004d00000c13d0000002401300370000000000301043b0000004d0030008c000005b60000a13d0000060c01000041000000000010043f0000001101000039000000040010043f0000060d01000041000016b100010430000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d0000006502000039000000000202041a000005b0022001970000000003000411000000000032004b000004f30000c13d000000000001004b000006be0000c13d000005b301000041000000e00010043f0000002001000039000000e40010043f0000002601000039000001040010043f000005e501000041000001240010043f000005e601000041000001440010043f000005e701000041000016b100010430000005b301000041000000e00010043f0000002001000039000000e40010043f000001040010043f0000063b01000041000001240010043f0000064901000041000016b100010430000000e00010043f000000000004004b000005540000613d000000000030043f000000000001004b0000064b0000c13d0000010001000039000006560000013d000005ad0050009c000005ad020000410000000002054019000005e4022000d1000005ad0010009c000005ad01008041000000c001100210000000000121019f000000000003004b000e00000005001d000f00000003001d000005ba0000c13d000000000204001916af16a50000040f0000006003100270000005ad093001970000000e03000029000000000039004b000000000403001900000000040940190000001f0540018f000005af064001980000000004630019000005cd0000613d000000000701034f000000007807043c0000000003830436000000000043004b0000051d0000c13d000005cd0000013d000005ad0010009c000005ad01008041000000c001100210000005ea011001c7000000000206001916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f0000002007400190000000e005700039000005370000613d000000e008000039000000000901034f000000009a09043c0000000008a80436000000000058004b000005330000c13d000000000006004b000005440000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000c00500043d00000001002001900000063b0000613d0000000f060000290000001f01400039000000600110018f000000e002100039000000400020043f000000200030008c000005930000813d000005ad0050009c000005ad05008041000005e4015000d1000016b1000104300000065102200197000001000020043f000000000001004b00000120010000390000010001006039000006560000013d0000000101000039000000010110018f000004680000013d0000065102200197000001000020043f000000000001004b00000120010000390000010001006039000006910000013d000005ad0010009c000005ad01008041000000c001100210000005ea011001c716af16aa0000040f000000e00a0000390000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f0000002007400190000000e005700039000005770000613d000000000801034f000000008908043c000000000a9a043600000000005a004b000005730000c13d000000000006004b000005840000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f00030000000103550000000100200190000006c00000613d0000001f01400039000000600110018f000000e001100039000000400010043f000000200040008c000001130000413d000000e00200043d00000000002104350000004001100210000005eb011001c7000016b00001042e000000d003000039000000000303041a00000100041001bf000000e00700043d000e00000007001d0000064e050000410000000000540435000005b00330019700000104041001bf000000000034043500000124031000390000000000730435000000440300003900000000003204350000016001100039000000400010043f000000000106001916af140c0000040f000000400100043d0000000e020000290000000000210435000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f000005b5011001c70000800d0200003900000002030000390000064f040000410000000f05000029000006370000013d000000000003004b000006e30000c13d000000010120011a000004680000013d0000060a011001c70000800902000039000000000500001916af16a50000040f0000006003100270000005ad093001970000000e03000029000000000039004b000000000403001900000000040940190000001f0540018f000005af064001980000000004630019000005cd0000613d000000000701034f000000007807043c0000000003830436000000000043004b000005c90000c13d000000000005004b000005da0000613d000000000661034f0000000305500210000000000704043300000000075701cf000000000757022f000000000606043b0000010005500089000000000656022f00000000055601cf000000000575019f0000000000540435000100000009001f00030000000103550000000f03000029000000000009004b000005f00000c13d000000400100043d0000000100200190000006190000613d0000000000310435000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f000005b5011001c70000800d0200003900000001030000390000062904000041000006370000013d000005ed0090009c0000076a0000213d0000001f0490003900000650044001970000003f044000390000065005400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000005ed0050009c0000076a0000213d00000001006001900000076a0000c13d000000400050043f000000000694043600000650049001980000001f0990018f000000000146001900000003050003670000060b0000613d000000000705034f000000007807043c0000000006860436000000000016004b000006070000c13d000000000009004b000005df0000613d000000000445034f0000000306900210000000000501043300000000056501cf000000000565022f000000000404043b0000010006600089000000000464022f00000000046401cf000000000454019f0000000000410435000005df0000013d000000440210003900000628030000410000000000320435000000240210003900000010030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b100010430000000ca02000039000000000302041a000005fe03300197000000000313019f000000000032041b000000e00010043f0000000001000414000005ad0010009c000005ad01008041000000c0011002100000064a011001c70000800d0200003900000001030000390000064b0400004116af16a50000040f0000000100200190000001130000613d000007710000013d0000000002350019000000000032004b000001130000213d000000000451034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b000006460000c13d000006d00000013d000006080200004100000000040000190000000003040019000000000402041a0000010005300039000000000045043500000001022000390000002004300039000000000014004b0000064d0000413d0000012001300039000000e00210008a000000e00100003916af11d30000040f0000002001000039000000400200043d000f00000002001d0000000002120436000000e00100003916af11e50000040f0000000f020000290000000001210049000005ad0010009c000005ad010080410000006001100210000005ad0020009c000005ad020080410000004002200210000000000121019f000016b00001042e000000400a00043d000006460100004100000000001a04350000000401a000390000000f020000290000000000210435000000c00300043d00000000010004140000000e04000029000000040040008c000007680000613d000005ad00a0009c000005ad0200004100000000020a40190000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f000000000003004b000f0000000a001d000007420000c13d0000060d011001c7000000000204001916af16a50000040f0000006003100270000005ad033001970000000f0a000029000007640000013d000005f50200004100000000040000190000000003040019000000000402041a0000010005300039000000000045043500000001022000390000002004300039000000000014004b000006880000413d0000012001300039000000e00210008a000000e00100003916af11d30000040f000000400300043d000700000003001d000001000130003900000120020000390000000000210435000000e00130003900000009020000290000000000210435000000c0013000390000000a020000290000000000210435000000a0013000390000000b02000029000000000021043500000080013000390000000c02000029000000000021043500000060013000390000000d02000029000000000021043500000040013000390000000e02000029000000000021043500000020013000390000000f0200002900000000002104350000000801000029000005b00110019700000000001304350000012002300039000000e00100003916af11e50000040f00000007020000290000000001210049000005ad0010009c000005ad01008041000005ad0020009c000005ad0200804100000060011002100000004002200210000000000121019f000016b00001042e16af11f70000040f000007710000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b000006cc0000c13d000000000005004b000006dd0000613d000000000464034f0000000305500210000000000602043300000000065601cf000000000656022f000000000404043b0000010005500089000000000454022f00000000045401cf000000000464019f00000000004204350000006002300210000005ad0010009c000005ad010080410000004001100210000000000121019f000016b1000104300000000a040000390000000101000039000000010030019000000000054400a9000000010400603900000000011400a900000001033002720000000004050019000006e50000c13d000000000001004b000006fa0000c13d0000060c01000041000000000010043f0000001201000039000000040010043f0000060d01000041000016b1000104300000065104400197000002200040043f000000000003004b00000020040000390000000004006039000007050000013d00000000011200d9000004680000013d000005f5050000410000000004000019000000000605041a0000022007400039000000000067043500000001055000390000002004400039000000000034004b000006fe0000413d0000003f034000390000065003300197000006330030009c0000076a0000213d0000020003300039000000400030043f000001e00020043f000000dd02000039000000000202041a000005b0002001980000071d0000c13d0000000f02000029000000a403200039000000000231034f000000000202043b000006370020009c000007870000413d000000400100043d00000044021000390000064503000041000000000032043500000024021000390000001f030000390000061e0000013d000001a00100043d000d00000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000201043b0000000d0020006c00000c330000813d0000000201000367000001800300043d000000000032004b000007100000a13d0000000f020000290000004402200039000000000221034f000000000202043b000001200300043d000000000023004b000007100000613d000000400100043d00000064021000390000063503000041000000000032043500000044021000390000063603000041000000000032043500000024021000390000002403000039000001ae0000013d00000647011001c70000800902000039000d00000003001d000000000500001916af16a50000040f0000006003100270000005ad033001970000000d0030006c0000000d0400002900000000040340190000001f0540018f000005af064001980000000f0a00002900000000046a0019000007570000613d000000000701034f00000000080a0019000000007907043c0000000008980436000000000048004b000007530000c13d000000000005004b000007640000613d000000000661034f0000000305500210000000000704043300000000075701cf000000000757022f000000000606043b0000010005500089000000000656022f00000000055601cf000000000575019f0000000000540435000100000003001f00030000000103550000000100200190000007760000613d0000064800a0009c000007700000413d0000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b1000104300000004000a0043f000000c00100043d000005ad0010009c000005ad01008041000005e4011000d1000016b00001042e000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b000007820000c13d000006d00000013d0000002004300039000000000341034f000000000303043b000006370030009c000007930000413d000000400100043d00000044021000390000064403000041000000000032043500000024021000390000001d030000390000061e0000013d0000002005400039000000000451034f000000000404043b000006380040009c000007aa0000413d000000400100043d000000440210003900000643030000410000000000320435000005b30200004100000000002104350000002402100039000000200300003900000000003204350000000402100039000006230000013d000000400100043d00000044021000390000062a030000410000000000320435000000240210003900000014030000390000061e0000013d000000e00650008a000000000561034f000000000505043b000005b00050009c000001130000213d000000000005004b000007b80000c13d000000400100043d000000440210003900000642030000410000000000320435000000240210003900000017030000390000061e0000013d0000004007600039000000000671034f000000000606043b000000000006004b000008350000c13d000000400100043d000000440210003900000641030000410000061b0000013d0000000b02000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c70000000a0200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000b05700029000007db0000613d000000000801034f0000000b09000029000000008a08043c0000000009a90436000000000059004b000007d70000c13d000000000006004b000007e80000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f00030000000103550000000100200190000008d60000613d0000001f01400039000000600210018f0000000b01200029000000000021004b00000000020000390000000102004039000005ed0010009c0000076a0000213d00000001002001900000076a0000c13d000000400010043f000000200030008c000001130000413d0000000b010000290000000001010433000005b00010009c000001130000213d000000e00500003900000000020004110000000f030000290000000e0400002916af15810000040f000000d501000039000000000101041a000e00000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000201043b0000000e0120006c000009960000a13d000000d603000039000000000303041a000000000032004b000009a70000813d000000d202000039000000000202041a000000db03000039000000000303041a000000000023004b000009b20000813d000000d702000039000000000202041a000000000002004b00000000030000190000082b0000613d000005b002200198000006ee0000613d000000dd03000039000000000303041a0000000004000411000000000343013f000005b003300197000005b00220012900000000032300d9000000000031004b000009b90000a13d000000ce01000039000000000101041a000000ff00100190000009c00000c13d000000400100043d00000044021000390000063103000041000009990000013d0000002008700039000000000781034f000000000707043b000000000007004b000008e70000c13d000000400100043d000000440210003900000640030000410000061b0000013d0000000902000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c7000000080200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000905700029000008580000613d000000000801034f0000000909000029000000008a08043c0000000009a90436000000000059004b000008540000c13d000000000006004b000008650000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f00030000000103550000000100200190000008f00000613d0000001f01400039000000600210018f0000000901200029000000000021004b00000000020000390000000102004039000005ed0010009c0000076a0000213d00000001002001900000076a0000c13d000000400010043f000000200040008c000001130000413d00000009010000290000000001010433000005b00010009c000001130000213d000000e00500003900000000020004110000000e030000290000000d0400002916af15810000040f000000d501000039000000000101041a000d00000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000201043b0000000d0120006c000009960000a13d000000d603000039000000000303041a000000000032004b000009a70000813d000000d202000039000000000202041a000000db03000039000000000303041a000000000023004b000009b20000813d000000d702000039000000000202041a000000000002004b0000000003000019000008a80000613d000005b002200198000006ee0000613d000000dd03000039000000000303041a0000000004000411000000000343013f000005b003300197000005b00220012900000000032300d9000000000031004b000009b90000a13d000000c00100043d0000000f020000290000000000210435000000cf01000039000000200010043f000000c00100043d000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000121019f0000060f011001c7000080100200003916af16aa0000040f0000000100200190000001130000613d000000400200043d0000060e0020009c0000076a0000213d000000000101043b0000006003200039000000400030043f000000000301041a000005b00330019800000000033204360000000104100039000000000404041a000000000043043500000040022000390000000201100039000000000101041a000000ff0110018f000000000012043500000aa40000c13d000000400100043d000000440210003900000626030000410000000000320435000000240210003900000015030000390000061e0000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b000008e20000c13d000006d00000013d000000000067004b000009010000a13d000000400100043d00000044021000390000063f030000410000000000320435000000240210003900000019030000390000061e0000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b000008fc0000c13d000006d00000013d0000002008800039000000000981034f000000000909043b000000000079004b0000090a0000a13d000000400100043d00000044021000390000063e030000410000078f0000013d000000000a24001900000000003a004b0000099d0000813d000000650a000039000000000a0a041a000005b00aa00197000000000b0004110000000000ba004b000009ae0000c13d000000d00b000039000000000a0b041a000005fe0aa0019700000000055a019f00000000005b041b000000600580008a000000000551034f000000000505043b000000d10a00003900000000005a041b000000d205000039000000000065041b000000d305000039000000000075041b000000d405000039000000000095041b000000d505000039000000000025041b000000d602000039000000000032041b000000d702000039000000000042041b0000008002800039000000000221034f0000000e03000029000000230330008a000000000202043b00000605043001970000060505200197000000000645013f000000000045004b00000000040000190000060504002041000000000032004b00000000030000190000060503004041000006050060009c000000000403c019000000000004004b0000013c0000613d0000000f032000290000000402300039000000000221034f000000000202043b000005ed0020009c0000013c0000213d0000002403300039000000000420007900000605054001970000060506300197000000000756013f000000000056004b00000000050000190000060505004041000000000043004b00000000040000190000060504002041000006050070009c000000000504c019000000000005004b0000013c0000c13d000000d804000039000000000404041a000000010040019000000001054002700000007f0550618f0000001f0050008c00000000060000390000000106002039000000000464013f00000001004001900000047d0000c13d000000c00400043d000000200050008c0000096f0000413d000000d80600003900000000006404350000001f042000390000000504400270000006060640009a000000200020008c000005f506004041000000c00400043d0000001f055000390000000505500270000006060550009a000000000056004b0000096f0000813d000000000046041b0000000106600039000000000056004b0000096b0000413d0000001f0020008c00000a050000a13d000000d80500003900000000005404350000065006200197000005f505000041000000c00700043d000000000067004b000009880000813d00000652057001670000002007700039000000000076004b00000000070620190000000007750019000005f50500004100000005077002700000063c0770009a0000000008340019000000000881034f000000000808043b000000000085041b00000020044000390000000105500039000000000075004b000009800000c13d000000000026004b000009930000813d0000000306200210000000f80660018f000006520660027f00000652066001670000000003340019000000000131034f000000000101043b000000000161016f000000000015041b000000010120021000000001011001bf00000a100000013d000000400100043d00000044021000390000062b030000410000000000320435000000240210003900000018030000390000061e0000013d000000400100043d00000064021000390000063903000041000000000032043500000044021000390000063a03000041000000000032043500000024021000390000002b03000039000001ae0000013d000000400100043d00000044021000390000062c03000041000000000032043500000024021000390000000e030000390000061e0000013d000000400100043d00000044021000390000063b030000410000079b0000013d000000400100043d00000044021000390000062d030000410000000000320435000000240210003900000016030000390000061e0000013d000000400100043d00000044021000390000062e030000410000000000320435000000240210003900000011030000390000061e0000013d0000000101000039000000000201041a000000020020008c000009c80000c13d000000400100043d00000044021000390000063003000041000007190000013d0000000202000039000000000021041b000000cd01000039000000000201041a000000cc01000039000000000101041a000005b001100197000b00000001001d000e00000002001d16af120c0000040f000000000300041600000000021300a9000000000003004b000009d90000613d00000000033200d9000000000013004b000004d00000c13d0000061f0120012a000900000001001d0000000c0010006c00000a790000813d000000400100043d00000044021000390000062f03000041000008ec0000013d001100000001001d0000000001000415000000110110008a0004000500100218000005ef010000410000000000100443000000000100041000000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000000001004b00000a970000c13d0000000601000029000000ff0110018f000000010010008c00000004010000290000000501100270000000000100003f000000010100603f00000a9a0000c13d000000c00100043d000000a00200043d000000000002004b000000a80000c13d000001000200008a000000060220017f00000001022001bf000000ab0000013d000000000002004b00000a0a0000613d0000000003340019000000000131034f000000000401043b0000000301200210000006520110027f0000065201100167000000000114016f0000000102200210000000000121019f000000d802000039000000000012041b000006120100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000000001004b000004d00000613d00000613020000410000000000200443000000010110008a00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000005b001100197000000dd03000039000000000203041a000005fe02200197000000000112019f000000000013041b0000002002000039000000400100043d0000000002210436000000d003000039000000000303041a000005b0033001970000000000320435000000d102000039000000000202041a00000040031000390000000000230435000000d202000039000000000202041a00000060031000390000000000230435000000d302000039000000000202041a00000080031000390000000000230435000000d402000039000000000202041a000000a0031000390000000000230435000000d502000039000000000202041a000000c0031000390000000000230435000000d602000039000000000202041a000000e0031000390000000000230435000000d702000039000000000202041a00000120031000390000012004000039000000000043043500000100031000390000000000230435000000d802000039000000000402041a000000010540019000000001024002700000007f0220618f0000001f0020008c00000000030000390000000103002039000000000334013f00000001003001900000047d0000c13d000001400310003900000000002304350000016003100039000000000005004b00000bfd0000613d000000d804000039000000000040043f000000000002004b000000000400001900000c020000613d000005f50500004100000000040000190000000006340019000000000705041a000000000076043500000001055000390000002004400039000000000024004b00000a710000413d00000c020000013d0000000c010000290008061f001000d5000000000001004b00000a810000613d00000008020000290000000c012000fa0000061f0010009c000004d00000c13d0000000b010000290000000e0200002916af120c0000040f000000000001004b000006ee0000613d00000008021000f90000000d01000029000c00000002001d16af13050000040f0000062001000041000000400200043d000e00000002001d000000000012043500000000010004140000000a02000029000000040020008c00000b660000c13d0000000103000031000000200030008c0000002004000039000000000403401900000b910000013d00000004010000290000000501100270000000000100003f000000400100043d0000006402100039000005f10300004100000000003204350000004402100039000005f203000041000000000032043500000024021000390000002e03000039000001ae0000013d0000000101000039000d00000001001d000000000101041a000000020010008c000009c40000613d00000002010000390000000102000039000000000012041b000000cd01000039000000000201041a000000cc01000039000000000101041a000005b001100197000700000001001d000900000002001d16af120c0000040f000000000300041600000000021300a9000000000003004b00000abb0000613d00000000033200d9000000000013004b000004d00000c13d0000061f0120012a0000000a0010006c000009dd0000413d0000000a010000290006061f001000d5000000000001004b00000ac60000613d00000006020000290000000a012000fa0000061f0010009c000004d00000c13d0000000701000029000000090200002916af120c0000040f000000000001004b000006ee0000613d00000006021000f90000000b0100002916af13050000040f000000c00100043d0000000f020000290000000000210435000000cf01000039000000200010043f000000c00100043d000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000121019f0000060f011001c7000080100200003916af16aa0000040f0000000100200190000001130000613d000000400300043d0000060e0030009c0000076a0000213d000000000401043b0000006001300039000000400010043f000000000104041a000005b00110019700000000051304360000000102400039000000000202041a000000000025043500000040033000390000000204400039000000000404041a000000ff0440018f000b00000004001d000000000043043516af120c0000040f0009000c001000bd0000000c0000006b00000afb0000613d00000009030000290000000c023000fa000000000012004b000004d00000c13d0000000b010000290000004d0010008c000004d00000213d0000000b0000006b00000b0c0000613d0000000a01000039000d00010000003d0000000b04000029000000010040019000000000021100a90000000101006039000d000d001000bd000b00010040027a000000000102001900000b020000c13d0000000d0000006b000006ee0000613d000000400200043d0000062001000041000b00000002001d000000000012043500000000010004140000000802000029000000040020008c00000cc50000c13d0000000104000031000000200040008c000000200400803900000cf00000013d00000651066001970000000000650435000000000004004b000000200600003900000000060060390000003f0460003900000650054001970000000004350019000000000054004b00000000050000390000000105004039000005ed0040009c0000076a0000213d00000001005001900000076a0000c13d000000400040043f00000100011000390000000000310435000000dd01000039000000000101041a000005b00010019800000c140000c13d00000002020003670000002401200370000000000101043b000000a401100039000000000312034f000000000303043b000005f80030009c000007160000213d0000002001100039000000000412034f000000000404043b000005f80040009c0000078c0000213d0000002001100039000000000512034f000000000505043b000005f90050009c000007980000213d000000e00610008a000000000162034f000000000701043b000005b00070009c000001130000213d000000400100043d000000000007004b000007b20000613d0000004007600039000000000672034f000000000606043b000000000006004b000007be0000613d0000002008700039000000000782034f000000000707043b000000000007004b0000083b0000613d000000000067004b000008ea0000213d0000002006800039000000000262034f000000000202043b000000000072004b000009070000213d0000000002350019000000000042004b0000099e0000813d000000c00200043d000000000302041a0000ff000030019000000d970000c13d00000064021000390000061803000041000000000032043500000044021000390000061903000041000009a30000013d0000000e02000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c70000000a0200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000e0570002900000b800000613d000000000801034f0000000e09000029000000008a08043c0000000009a90436000000000059004b00000b7c0000c13d000000000006004b00000b8d0000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000c3a0000613d0000001f01400039000000600110018f0000000e04100029000000000014004b00000000020000390000000102004039000d00000004001d000005ed0040009c0000076a0000213d00000001002001900000076a0000c13d0000000d02000029000000400020043f000000200030008c000001130000413d0000000e020000290000000002020433000005b00020009c000001130000213d00000621040000410000000d0500002900000000004504350000000404500039000000000500041100000000005404350000000004000414000000040020008c00000bd90000613d0000000d01000029000005ad0010009c000005ad010080410000004001100210000005ad0040009c000005ad04008041000000c003400210000000000113019f0000060d011001c716af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000d0570002900000bc60000613d000000000801034f0000000d09000029000000008a08043c0000000009a90436000000000059004b00000bc20000c13d000000000006004b00000bd30000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000c4b0000613d0000001f01400039000000600110018f0000000d01100029000005ed0010009c0000076a0000213d000000400010043f000000200030008c000001130000413d0000000d010000290000000001010433000e00000001001d00000009010000290000000f0200002916af138b0000040f0000000e0000006b0000000e02000029000000640200603900000000010004160000000003020019000e0000001200ad000000000001004b00000bf00000613d0000000e011000f9000000000031004b000004d00000c13d000000400200043d0000062201000041000f00000002001d000000000012043500000000010004140000000a02000029000000040020008c00000c5c0000c13d0000000103000031000000200030008c0000002004000039000000000403401900000c870000013d00000651044001970000000000430435000000000002004b00000020040000390000000004006039000005ad0010009c000005ad0100804100000040011002100000016002400039000005ad0020009c000005ad020080410000006002200210000000000112019f0000000002000414000005ad0020009c000005ad02008041000000c002200210000000000121019f0000060a011001c70000800d0200003900000001030000390000063d04000041000006370000013d0000000001020433000300000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000030010006c00000c330000813d000000020200036700000006030000290000000003030433000000000031004b00000b2f0000a13d0000002401200370000000000101043b0000004401100039000000000112034f000000000101043b00000004030000290000000003030433000000000013004b000007380000c13d00000b2f0000013d000000400100043d00000044021000390000063403000041000000000032043500000024021000390000001a030000390000061e0000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000c460000c13d000006d00000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000c570000c13d000006d00000013d0000000f02000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c70000000a0200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000f0570002900000c760000613d000000000801034f0000000f09000029000000008a08043c0000000009a90436000000000059004b00000c720000c13d000000000006004b00000c830000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000d110000613d0000001f01400039000000600210018f0000000f01200029000000000021004b00000000020000390000000102004039000005ed0010009c0000076a0000213d00000001002001900000076a0000c13d000000400010043f000000200030008c000001130000413d0000000f010000290000000001010433000005b00010009c000001130000213d0000000e02000029000027100220011a000f00000002001d16af151b0000040f00000000010004160000000f0110006c000004d00000413d0000000c0210006c000004d00000413d000000d001000039000000000101041a000005b00110019716af151b0000040f000000400100043d0000006002100039000000c00600043d0000000c03000029000000000032043500000040021000390000000f03000029000000000032043500000020021000390000000003000416000000000032043500000009020000290000000000210435000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f00000610011001c70000800d0200003900000003030000390000062504000041000000000500041116af16a50000040f0000000100200190000001130000613d0000000101000039000000000011041b000007710000013d0000000b02000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c7000000080200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000b0570002900000cdf0000613d000000000801034f0000000b09000029000000008a08043c0000000009a90436000000000059004b00000cdb0000c13d000000000006004b00000cec0000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000d220000613d0000001f01400039000000600110018f0000000b03100029000000000013004b00000000020000390000000102004039000700000003001d000005ed0030009c0000076a0000213d00000001002001900000076a0000c13d0000000702000029000000400020043f000000200040008c000001130000413d0000000b020000290000000002020433000005b00020009c000001130000213d0000062103000041000000070400002900000000003404350000000403400039000000000400041100000000004304350000000003000414000000040020008c00000d330000c13d0000000701100029000005ed0010009c0000076a0000213d000000400010043f00000d650000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000d1d0000c13d000006d00000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000d2e0000c13d000006d00000013d0000000701000029000005ad0010009c000005ad010080410000004001100210000005ad0030009c000005ad03008041000000c003300210000000000113019f0000060d011001c716af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f0000002007400190000000070570002900000d4c0000613d000000000801034f0000000709000029000000008a08043c0000000009a90436000000000059004b00000d480000c13d000000000006004b00000d590000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000d860000613d0000001f01400039000000600110018f0000000701100029000005ed0010009c0000076a0000213d000000400010043f000000200030008c000001130000413d00000009020000290000000d012000fa00000007020000290000000002020433000000000002004b0000006402006039000d00000002001d0000000a0010002a000004d00000413d0000000a01100029000900000001001d0000000e0200002916af138b0000040f0000000d02000029000b000c002000bd0000000c0000006b00000d7a0000613d0000000b020000290000000c012000fa0000000d0010006c000004d00000c13d000000400200043d0000062201000041000e00000002001d000000000012043500000000010004140000000802000029000000040020008c00000da30000c13d0000000104000031000000200040008c000000200400803900000dce0000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000d920000c13d000006d00000013d000005fa0010009c00000e2c0000a13d0000060c0100004100000000001204350000004101000039000000040010043f000000c00100043d000005ad0010009c000005ad0100804100000040011002100000060d011001c7000016b1000104300000000e02000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c7000000080200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000e0570002900000dbd0000613d000000000801034f0000000e09000029000000008a08043c0000000009a90436000000000059004b00000db90000c13d000000000006004b00000dca0000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000e460000613d0000001f01400039000000600110018f0000000e02100029000000000012004b00000000010000390000000101004039000005ed0020009c0000076a0000213d00000001001001900000076a0000c13d000000400020043f000000200040008c000001130000413d0000000e010000290000000001010433000005b00010009c000001130000213d0000000b03000029000027100530011a0000002003200039000006230400004100000000004304350000006403200039000e00000005001d000000000053043500000044032000390000000000130435000000640100003900000000001204350000000001000411000005b0031001970000002401200039000d00000003001d0000000000310435000006240020009c0000076a0000213d000000a001200039000000400010043f0000000f0100002916af140c0000040f0000000e020000290000000c0120006b000004d00000413d000000400200043d000000200320003900000623040000410000000000430435000000640320003900000000001304350000000001000410000005b0011001970000004403200039000000000013043500000024012000390000000d03000029000000000031043500000064010000390000000000120435000006240020009c0000076a0000213d000000a001200039000000400010043f0000000f0100002916af140c0000040f000000400100043d00000060021000390000000a03000029000000000032043500000040021000390000000e03000029000000000032043500000020021000390000000c03000029000000000032043500000009020000290000000000210435000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f00000610011001c70000800d020000390000000303000039000006250400004100000000050004110000000f0600002916af16a50000040f0000000100200190000001130000613d00000cc20000013d0000002402100039000005fb0300004100000000003204350000004402100039000000c00300043d000000000400041400000060050000390000000000520435000005fc0200004100000000002104350000006402100039000000000002043500000004021000390000000000020435000005ad0010009c000005ad010080410000004001100210000005ad0040009c000005ad04008041000000c002400210000000000112019f000005fd011001c7000000000003004b00000e570000c13d000080060200003900000e5a0000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000e520000c13d000006d00000013d00008009020000390000800604000039000000010500003916af16a50000040f000000010020019000000e620000613d000000000101043b000000000001004b00000e8b0000c13d000000010100003100000e660000013d00030000000103550000006001100270000105ad0010019d000005ad01100197000000c00200043d0000000003120019000000000013004b000001130000213d00000650041001980000001f0510018f0000000306200367000000400200043d000000000342001900000e760000613d000000000706034f0000000008020019000000007907043c0000000008980436000000000038004b00000e720000c13d000000000005004b00000e830000613d000000000446034f0000000305500210000000000603043300000000065601cf000000000656022f000000000404043b0000010005500089000000000454022f00000000045401cf000000000464019f0000000000430435000005ad0010009c000005ad010080410000006001100210000005ad0020009c000005ad020080410000004002200210000000000112019f000016b100010430000005b0031001970000009701000039000000000401041a000005fe02400197000000000232019f000000000021041b000000c00100043d000005ef020000410000000000200443000200000004001d000000000141016f000300000003001d000000000131019f00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000000001004b0000013c0000613d000000400200043d000005ff01000041000600000002001d0000000001120436000400000001001d000000c00100043d0000000003010019000000020110017f00000003041001af0000000001000414000000040040008c00000ee60000613d0000000602000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f000000000003004b00000ec10000c13d00000601011001c7000000000204001916af16a50000040f0000006003100270000005ad0330019700000ee20000013d00000600011001c70000800902000039000100000003001d000000000500001916af16a50000040f0000006003100270000005ad03300197000000010030006c000000010400002900000000040340190000001f0540018f000005af06400198000000060460002900000ed50000613d000000000701034f0000000608000029000000007907043c0000000008980436000000000048004b00000ed10000c13d000000000005004b00000ee20000613d000000000661034f0000000305500210000000000704043300000000075701cf000000000757022f000000000606043b0000010005500089000000000656022f00000000055601cf000000000575019f0000000000540435000100000003001f0003000000010355000000010020019000000f050000613d0000000601000029000005ed0010009c0000076a0000213d0000000601000029000000400010043f000000c00300043d000000000003004b00000f160000c13d000000000100041a0000ff000010019000000f1a0000c13d000000060300002900000064013000390000061802000041000000000021043500000044013000390000061902000041000000000021043500000024013000390000002b020000390000000000210435000005b3010000410000000000130435000000040130003900000020020000390000000000210435000005ad0030009c000005ad030080410000004001300210000005b4011001c7000016b100010430000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000f110000c13d000006d00000013d000005ad0030009c000005ad03008041000005e4013000d1000016b1000104300000000101000039000000000011041b0000000b020000290000000d0020006b00000f2c0000c13d00000007020000290000000d0020006b00000f330000c13d0000000e01000029000005b00110019800000f440000c13d00000006030000290000004401300039000006170200004100000000002104350000002401300039000000120200003900000f390000013d000000060300002900000044013000390000060202000041000000000021043500000024013000390000001b0200003900000f390000013d000000060300002900000044013000390000060402000041000000000021043500000024013000390000001d020000390000000000210435000005b3010000410000000000130435000000040130003900000020020000390000000000210435000005ad0030009c000005ad03008041000000400130021000000603011001c7000016b10001043000000002020003670000002404200370000000000404043b0000000405400039000000000652034f000000000606043b000005b00060009c000001130000213d000000d008000039000000000708041a000005fe07700197000000000667019f000000000068041b0000002006500039000000000662034f000000000606043b000000d107000039000000000067041b0000004006500039000000000662034f000000000606043b000000d207000039000000000067041b0000006006500039000000000662034f000000000606043b000000d307000039000000000067041b0000008006500039000000000662034f000000000606043b000000d407000039000000000067041b000000a006500039000000000662034f000000000606043b000000d507000039000000000067041b000000c006500039000000000662034f000000000606043b000000d607000039000000000067041b000000e006500039000000000662034f000000000606043b000000d707000039000000000067041b0000010006500039000000000662034f00000000080000310000000007480049000000230970008a000000000706043b0000060506700197000006050a900197000000000ba6013f0000000000a6004b00000000060000190000060506004041000000000097004b000000000900001900000605090080410000060500b0009c000000000609c019000000000006004b000001130000c13d00000000044700190000000406400039000000000662034f000000000606043b000005ed0060009c000001130000213d000000000868004900000024044000390000060509800197000006050a400197000000000b9a013f00000000009a004b00000000090000190000060509004041000000000084004b000000000800001900000605080020410000060500b0009c000000000908c019000000000009004b000001130000c13d000000d808000039000000000808041a000000010080019000000001098002700000007f0990618f0000001f0090008c000000000a000039000000010a0020390000000008a8013f00000001008001900000047d0000c13d0000000005570019000000000552034f000000000805043b000000200090008c00000fbc0000413d000000d805000039000000000050043f0000001f058000390000000505500270000006060550009a000000200080008c000005f5050040410000001f079000390000000507700270000006060770009a000000000075004b00000fbc0000813d000000000005041b0000000105500039000000000075004b00000fb80000413d0000001f0080008c000000010780021000000fc60000a13d000000d808000039000000000080043f000006500960019800000fd10000c13d000005f508000041000000000a00001900000fdb0000013d000000000006004b000000000600001900000fcb0000613d000000000442034f000000000604043b0000000304800210000006520440027f0000065204400167000000000446016f000000000474019f00000fe70000013d000005f508000041000000000a000019000000000b4a0019000000000bb2034f000000000b0b043b0000000000b8041b0000000108800039000000200aa0003900000000009a004b00000fd30000413d000000000069004b00000fe60000813d0000000306600210000000f80660018f000006520660027f000006520660016700000000044a0019000000000442034f000000000404043b000000000464016f000000000048041b00000001047001bf000000d806000039000000000046041b000000cb04000039000000000604041a000000010060019000000001086002700000007f0880618f0000001f0080008c00000000070000390000000107002039000000000676013f00000001006001900000047d0000c13d0000004406200370000000000606043b0000000407600039000000000772034f000000000707043b0000001f0080008c0000100a0000a13d000000000040043f0000001f097000390000000509900270000006070990009a000000200070008c00000608090040410000001f088000390000000508800270000006070880009a000000000089004b0000100a0000813d000000000009041b0000000109900039000000000089004b000010060000413d0000001f0070008c00000001087002100000000309700210000000240a600039000010250000a13d000000000040043f000006500b7001970000000000b3004b0000102f0000813d000006520c300167000000200330003900000000003b004b00000000030b2019000000000c3c00190000060803000041000000050cc00270000006090dc0009a000000000c000019000000000eca0019000000000ee2034f000000000e0e043b0000000000e3041b000000200cc0003900000001033000390000000000d3004b0000101c0000c13d000010310000013d000000000007004b00000000030000190000102a0000613d0000000003a2034f000000000303043b000006520690027f0000065205600167000000000353016f000000000583019f0000103d0000013d0000060803000041000000000c00001900000000007b004b0000103c0000813d000000f80790018f000006520770027f00000652057001670000000006c600190000002406600039000000000662034f000000000606043b000000000556016f000000000053041b00000001058001bf000000000054041b000000cc06000039000000000306041a000005fe07300197000000000717019f000000000076041b000000a402200370000000000202043b000000cd06000039000000000026041b000000ce02000039000000000602041a00000651066001970000000f066001af000000000062041b000000800200003900000006080000290000000000280435000000d002000039000000000202041a000005b00220019700000080068000390000000000260435000000d102000039000000000202041a000000a0068000390000000000260435000000d202000039000000000202041a000000c0068000390000000000260435000000d302000039000000000202041a000000e0068000390000000000260435000000d402000039000000000202041a00000100068000390000000000260435000000d502000039000000000202041a00000120068000390000000000260435000000d602000039000000000202041a00000140068000390000000000260435000000d702000039000000000202041a00000180068000390000012007000039000000000076043500000160068000390000000000260435000000d802000039000000000702041a000000010870019000000001027002700000007f0220618f0000001f0020008c00000000060000390000000106002039000000000667013f00000001006001900000047d0000c13d0000000609000029000001a0069000390000000000260435000001c006900039000000000008004b000010930000613d000000d807000039000000000070043f000000000002004b0000000007000019000010980000613d000005f50800004100000000070000190000000009670019000000000a08041a0000000000a9043500000001088000390000002007700039000000000027004b0000108b0000413d000010980000013d00000651077001970000000000760435000000000002004b000000200700003900000000070060390000000002670019000000060620006a00000004070000290000000000670435000000010950019000000001075002700000007f0870018f000000000607001900000000060860190000001f0060008c000000000a000039000000010a002039000000000aa5013f0000000100a001900000047d0000c13d000000000a620436000000000009004b000010b00000c13d000006510450019700000000004a0435000000000008004b000000200220c0390000000005020019000010bf0000013d000000c00500043d0000000000450435000000c00400043d000000000074004b000010be0000813d000006080700004100000020044000390000000005240019000000000807041a00000000008504350000000107700039000000000064004b000010b60000413d000010bf0000013d000000000524001900000006070000290000006002700039000000c00400043d0000000f060000290000000000620435000000000234016f000000000112019f0000004002700039000000000012043500000000017500490000002001100039000005ad0010009c000005ad010080410000006001100210000005ad0070009c000005ad070080410000004002700210000000000121019f0000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f0000060a011001c70000800d0200003900000001030000390000060b0400004116af16a50000040f0000000100200190000001130000613d000000c00200043d000f00000002001d0000000d0020006c0000116b0000813d0000000f010000290000000503100210000b000c0030002d00000002040003670000000b01400360000000000101043b000005b00010009c000001130000213d000000000001004b000011b70000613d0000000a02300029000000000224034f000000000202043b000005b00020009c000001130000213d000000000002004b000011be0000613d0000000f06000029000000090060006c000011c20000813d00000008053000290000000503300029000000000634034f000000000354034f000000000303043b000000000406043b000000ff0040008c0000013c0000213d000000400500043d000e00000005001d0000060e0050009c0000076a0000213d0000000e060000290000006005600039000000400050043f0000004005600039000700000005001d00000000004504350000000002260436000600000002001d0000000000320435000000c00200043d0000000000120435000000cf01000039000000200010043f000000c00100043d000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000121019f0000060f011001c7000080100200003916af16aa0000040f0000000100200190000001130000613d0000000e020000290000000002020433000005b002200197000000000101043b000000000301041a000005fe03300197000000000223019f000000000021041b000000060200002900000000020204330000000103100039000000000023041b0000000201100039000000000201041a000006510220019700000007030000290000000003030433000000ff0330018f000000000232019f000000000021041b0000000b010000290000000201100367000000000101043b000e00000001001d000005b00010009c000001130000213d000000c00100043d0000000e020000290000000000210435000000cf01000039000000200010043f000000c00100043d000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000121019f0000060f011001c7000080100200003916af16aa0000040f0000000100200190000001130000613d000000000101043b000000400200043d0000000e030000290000000003320436000000000401041a000005b00440019700000000004304350000000103100039000000000303041a000000400420003900000000003404350000000201100039000000000101041a000000ff0110018f00000060032000390000000000130435000005ad0020009c000005ad0200804100000040012002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f00000610011001c70000800d020000390000000103000039000006110400004116af16a50000040f0000000100200190000001130000613d0000000f020000290000000102200039000010de0000013d000006120100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000000001004b000004d00000613d00000613020000410000000000200443000000010110008a00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000005b001100197000000dd03000039000000000203041a000005fe02200197000000000112019f000000000013041b000000800100043d000005b0061001970000006501000039000000000201041a000005fe03200197000000000363019f000000000031041b0000000001000414000005b005200197000005ad0010009c000005ad01008041000000c0011002100000060a011001c70000800d020000390000000303000039000006140400004116af16a50000040f0000000100200190000001130000613d000000a00100043d000000000001004b000007710000613d000000c00100043d000000000201041a0000065302200197000000000021041b000000400100043d00000001030000390000000000310435000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f000005b5011001c70000800d02000039000005b604000041000006370000013d000000000001042f000000400100043d000000440210003900000616030000410000000000320435000000240210003900000012030000390000061e0000013d000000400100043d00000044021000390000061503000041000009bc0000013d0000060c01000041000000000010043f0000003201000039000000040010043f0000060d01000041000016b100010430000006540010009c000011cd0000813d0000006001100039000000400010043f000000000001042d0000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b1000104300000001f0220003900000650022001970000000001120019000000000021004b00000000020000390000000102004039000005ed0010009c000011df0000213d0000000100200190000011df0000c13d000000400010043f000000000001042d0000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b10001043000000000430104340000000001320436000000000003004b000011f10000613d000000000200001900000000052100190000000006240019000000000606043300000000006504350000002002200039000000000032004b000011ea0000413d000000000231001900000000000204350000001f0230003900000650022001970000000001210019000000000001042d000005b0061001970000006501000039000000000201041a000005fe03200197000000000363019f000000000031041b0000000001000414000005b005200197000005ad0010009c000005ad01008041000000c0011002100000060a011001c70000800d020000390000000303000039000006140400004116af16a50000040f00000001002001900000120a0000613d000000000001042d0000000001000019000016b1000104300003000000000002000300000002001d000000400b00043d000006550200004100000000052b04360000000003000414000005b002100197000000040020008c0000121a0000c13d0000000103000031000000a00030008c000000a0040000390000000004034019000012480000013d000100000005001d000005ad00b0009c000005ad0100004100000000010b40190000004001100210000005ad0030009c000005ad03008041000000c003300210000000000113019f00000601011001c700020000000b001d16af16aa0000040f000000020b0000290000006003100270000005ad03300197000000a00030008c000000a00400003900000000040340190000001f0640018f000000e00740019000000000057b0019000012360000613d000000000801034f00000000090b0019000000008a08043c0000000009a90436000000000059004b000012320000c13d000000000006004b000012430000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f00030000000103550000000100200190000012ad0000613d00000001050000290000001f01400039000001e00210018f0000000001b20019000000000021004b00000000020000390000000102004039000005ed0010009c0000127d0000213d00000001002001900000127d0000c13d000000400010043f0000009f0030008c0000127b0000a13d00000000020b0433000006560020009c0000127b0000213d0000008002b000390000000002020433000006560020009c0000127b0000213d0000000003050433000005ee0030009c000012830000213d000000000003004b000012830000613d000000000002004b000012890000613d000200000003001d0000006002b000390000000002020433000100000002001d000000000002004b0000128f0000613d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000012950000613d000000000101043b000000030210006c0000000201000029000012960000413d000000010020006b0000129c0000a13d000000000001042d0000000001000019000016b1000104300000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b10001043000000044021000390000065a03000041000000000032043500000024021000390000000e03000039000012a20000013d00000044021000390000065903000041000000000032043500000024021000390000000b03000039000012a20000013d00000044021000390000065803000041000000000032043500000024021000390000001203000039000012a20000013d000000000001042f0000060c01000041000000000010043f0000001101000039000000040010043f0000060d01000041000016b100010430000000400100043d00000044021000390000065703000041000000000032043500000024021000390000001c030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b1000104300000001f0530018f000005af06300198000000400200043d0000000004620019000012b80000613d000000000701034f0000000008020019000000007907043c0000000008980436000000000048004b000012b40000c13d000000000005004b000012c50000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000006001300210000005ad0020009c000005ad020080410000004002200210000000000112019f000016b100010430000006120100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000012e90000613d000000000101043b000000000001004b000012ea0000613d00000613020000410000000000200443000000010110008a00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c70000800b0200003916af16aa0000040f0000000100200190000012e90000613d000000000101043b000005b001100197000000000001042d000000000001042f0000060c01000041000000000010043f0000001101000039000000040010043f0000060d01000041000016b100010430000000d702000039000000000202041a000000000002004b000012fd0000613d000005b002200198000012ff0000613d000000dd03000039000000000303041a000000000113013f000005b001100197000005b00220012900000000012100d9000000000001042d0000000001000019000000000001042d0000060c01000041000000000010043f0000001201000039000000040010043f0000060d01000041000016b1000104300002000000000002000200000002001d000100000001001d00000627010000410000000000100443000000000100041000000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c70000800a0200003916af16aa0000040f0000000100200190000013650000613d000000000101043b0000000203000029000000000031004b000013660000413d00000000010004140000000102000029000005b004200197000000040040008c000013230000c13d00000001020000390000000101000031000000000001004b000013340000c13d0000135c0000013d000005ad0010009c000005ad01008041000000c001100210000000000003004b0000132c0000613d0000060a011001c7000080090200003900000000050000190000132d0000013d000000000204001916af16a50000040f00030000000103550000006001100270000105ad0010019d000005ad01100197000000000001004b0000135c0000613d000006480010009c0000135f0000813d0000001f0410003900000650044001970000003f044000390000065005400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000005ed0050009c0000135f0000213d00000001006001900000135f0000c13d000000400050043f000000000614043600000650031001980000001f0410018f000000000136001900000003050003670000134f0000613d000000000705034f000000007807043c0000000006860436000000000016004b0000134b0000c13d000000000004004b0000135c0000613d000000000335034f0000000304400210000000000501043300000000054501cf000000000545022f000000000303043b0000010004400089000000000343022f00000000034301cf000000000353019f00000000003104350000000100200190000013770000613d000000000001042d0000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b100010430000000000001042f000000400100043d00000044021000390000065d03000041000000000032043500000024021000390000001d030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b100010430000000400100043d00000064021000390000065b03000041000000000032043500000044021000390000065c03000041000000000032043500000024021000390000003a030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad010080410000004001100210000005b4011001c7000016b1000104300003000000000002000200000002001d000300000001001d0000000001000411000000000010043f000000dc01000039000000200010043f0000000001000414000005ad0010009c000005ad01008041000000c0011002100000060f011001c7000080100200003916af16aa0000040f0000000100200190000013e80000613d000000000101043b000000000101041a0000000302000029000000000021001a000013ea0000413d0000000001210019000000020010006c000013f00000213d000000db01000039000000000301041a000000000023001a000013ea0000413d0000000004230019000000d201000039000000000101041a000000000014004b000013f40000213d000000d401000039000000000101041a000000000021004b000013fb0000213d000000d901000039000000000201041a000000010220003a000013ea0000613d000100000004001d000200000003001d000000000021041b0000000001000411000000000010043f000000dc01000039000000200010043f0000000001000414000005ad0010009c000005ad01008041000000c0011002100000060f011001c7000080100200003916af16aa0000040f0000000100200190000013e80000613d000000000101043b000000000101041a000000000001004b000000db0300003900000002040000290000000105000029000013d00000c13d000000da01000039000000000201041a000000010220003a000013ea0000613d000000000021041b000000000045004b000013ea0000413d000000000053041b0000000001000411000000000010043f000000dc01000039000000200010043f0000000001000414000005ad0010009c000005ad01008041000000c0011002100000060f011001c7000080100200003916af16aa0000040f0000000100200190000013e80000613d000000000101043b000000000201041a0000000303000029000000000032001a000013ea0000413d0000000002320019000000000021041b000000000001042d0000000001000019000016b1000104300000060c01000041000000000010043f0000001101000039000000040010043f0000060d01000041000016b100010430000000400100043d00000044021000390000066003000041000013f70000013d000000400100043d00000044021000390000065f03000041000000000032043500000024021000390000001b03000039000014010000013d000000400100043d00000044021000390000065e030000410000000000320435000000240210003900000016030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b1000104300004000000000002000000400400043d000006610040009c000014cf0000813d000005b0051001970000004001400039000000400010043f0000002001400039000006620300004100000000003104350000002001000039000000000014043500000000230204340000000001000414000000040050008c000014470000c13d0000000101000032000014820000613d000005ed0010009c000014cf0000213d0000001f0310003900000650033001970000003f033000390000065003300197000000400a00043d00000000033a00190000000000a3004b00000000040000390000000104004039000005ed0030009c000014cf0000213d0000000100400190000014cf0000c13d000000400030043f00000000051a043600000650021001980000001f0310018f00000000012500190000000304000367000014390000613d000000000604034f000000006706043c0000000005750436000000000015004b000014350000c13d000000000003004b000014830000613d000000000224034f0000000303300210000000000401043300000000043401cf000000000434022f000000000202043b0000010003300089000000000232022f00000000023201cf000000000242019f0000000000210435000014830000013d000200000004001d000005ad0030009c000005ad030080410000006003300210000005ad0020009c000005ad020080410000004002200210000000000223019f000005ad0010009c000005ad01008041000000c001100210000000000112019f000100000005001d000000000205001916af16a50000040f00030000000103550000006003100270000105ad0030019d000005ad043001980000149a0000613d0000001f03400039000005ae033001970000003f033000390000066303300197000000400a00043d00000000033a00190000000000a3004b00000000050000390000000105004039000005ed0030009c000014cf0000213d0000000100500190000014cf0000c13d000000400030043f0000001f0540018f00000000034a0436000005af064001980000000004630019000014740000613d000000000701034f0000000008030019000000007907043c0000000008980436000000000048004b000014700000c13d000000000005004b0000149c0000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000149c0000013d000000600a0000390000000002000415000000040220008a000000050220021000000000010a0433000000000001004b000014a40000c13d00020000000a001d000005ef010000410000000000100443000000040100003900000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000015010000613d0000000002000415000000040220008a000014b70000013d000000600a000039000000800300003900000000010a04330000000100200190000014eb0000613d0000000002000415000000030220008a0000000502200210000000000001004b000014a70000613d000000050220027000000000020a001f000014c10000013d00020000000a001d000005ef010000410000000000100443000000010100002900000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000015010000613d0000000002000415000000030220008a0000000502200210000000000101043b000000000001004b000000020a000029000015020000613d00000000010a0433000000050220027000000000020a001f000000000001004b000014ce0000613d000005ee0010009c000014d50000213d000000200010008c000014d50000413d0000002001a000390000000001010433000000000001004b0000000002000039000000010200c039000000000021004b000014d50000c13d000000000001004b000014d70000613d000000000001042d0000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b1000104300000000001000019000016b100010430000000400100043d00000064021000390000066403000041000000000032043500000044021000390000066503000041000000000032043500000024021000390000002a030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad010080410000004001100210000005b4011001c7000016b100010430000000000001004b000015130000c13d000000400300043d000100000003001d000005b30100004100000000001304350000000401300039000000200200003900000000002104350000002402300039000000020100002916af11e50000040f00000001020000290000000001210049000005ad0010009c000005ad01008041000005ad0020009c000005ad0200804100000060011002100000004002200210000000000121019f000016b100010430000000000001042f000000400100043d00000044021000390000066603000041000000000032043500000024021000390000001d030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b100010430000005ad0030009c000005ad030080410000004002300210000005ad0010009c000005ad010080410000006001100210000000000121019f000016b1000104300003000000000002000100000002001d000200000001001d0000009701000039000000000101041a000005ef020000410000000000200443000005b001100197000300000001001d00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000015590000613d000000000101043b000000000001004b0000155a0000613d000000400500043d000006670100004100000000001504350000000201000029000005b0011001970000000402500039000000000012043500000000010004140000000304000029000000040040008c000015550000613d000005ad0050009c000200000005001d000005ad0200004100000000020540190000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f0000000103000029000000000003004b0000154c0000613d00000647011001c7000080090200003900000000050000190000154e0000013d0000060d011001c7000000000204001916af16a50000040f00030000000103550000006003100270000105ad0030019d00000001002001900000000205000029000015620000613d000006480050009c0000155c0000813d000000400050043f000000000001042d000000000001042f0000000001000019000016b1000104300000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b100010430000005ad033001970000001f0530018f000005af06300198000000400200043d00000000046200190000156e0000613d000000000701034f0000000008020019000000007907043c0000000008980436000000000048004b0000156a0000c13d000000000005004b0000157b0000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000006001300210000005ad0020009c000005ad020080410000004002200210000000000121019f000016b1000104300003000000000002000300000005001d000200000001001d00000000010004100000006005100210000000400100043d00000020061000390000000000560435000000600220021000000034051000390000000000250435000100000004001d000000c002400210000000680510003900000000002504350000004802100039000000000032043500000050020000390000000000210435000006680010009c000016100000813d0000008002100039000000400020043f000005ad0060009c000005ad0600804100000040026002100000000001010433000005ad0010009c000005ad010080410000006001100210000000000121019f0000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f0000060a011001c7000080100200003916af16aa0000040f00000001002001900000160e0000613d000000000101043b0000066902000041000000000020043f0000001c0010043f0000000001000414000005ad0010009c000005ad01008041000000c0011002100000066a011001c7000080100200003916af16aa0000040f00000001002001900000160e0000613d000000400200043d000000000101043b00000003050000290000000034050434000000410040008c000016160000c13d000000400450003900000000040404330000066c0040009c000016260000213d0000006005500039000000000505043300000000030304330000006006200039000000000046043500000040042000390000000000340435000000f803500270000000200420003900000000003404350000000000120435000000000000043f000005ad0020009c000005ad0200804100000040012002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f0000066d011001c7000000010200003916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0540018f0000002004400190000015e60000613d000000000601034f0000000007000019000000006806043c0000000007870436000000000047004b000015e20000c13d000000000005004b000015f30000613d000000000641034f0000000305500210000000000704043300000000075701cf000000000757022f000000000606043b0000010005500089000000000656022f00000000055601cf000000000575019f0000000000540435000100000003001f00030000000103550000000100200190000016390000613d000000000100043d000005b000100198000016570000613d000000020110014f000005b0001001980000165b0000c13d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f00000001002001900000165f0000613d0000000102000029000005ed02200197000000000101043b000000000021004b000016600000813d000000000001042d0000000001000019000016b1000104300000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b10001043000000044012000390000066b03000041000000000031043500000024012000390000001f030000390000000000310435000005b3010000410000000000120435000000040120003900000020030000390000000000310435000005ad0020009c000005ad02008041000000400120021000000603011001c7000016b100010430000000640120003900000671030000410000000000310435000000440120003900000672030000410000000000310435000000240120003900000022030000390000000000310435000005b3010000410000000000120435000000040120003900000020030000390000000000310435000005ad0020009c000005ad020080410000004001200210000005b4011001c7000016b1000104300000001f0530018f000005af06300198000000400200043d0000000004620019000016440000613d000000000701034f0000000008020019000000007907043c0000000008980436000000000048004b000016400000c13d000000000005004b000016510000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000006001300210000005ad0020009c000005ad020080410000004002200210000000000112019f000016b100010430000000400100043d00000044021000390000067003000041000016630000013d000000400100043d00000044021000390000066e03000041000016630000013d000000000001042f000000400100043d00000044021000390000066f030000410000000000320435000000240210003900000018030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b100010430000000000001042f000005ad0010009c000005ad010080410000004001100210000005ad0020009c000005ad020080410000006002200210000000000112019f0000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f0000060a011001c7000080100200003916af16aa0000040f0000000100200190000016850000613d000000000101043b000000000001042d0000000001000019000016b10001043000000000050100190000000000200443000000050030008c000016950000413d000000040100003900000000020000190000000506200210000000000664001900000005066002700000000006060031000000000161043a0000000102200039000000000031004b0000168d0000413d000005ad0030009c000005ad0300804100000060013002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f00000673011001c7000000000205001916af16aa0000040f0000000100200190000016a40000613d000000000101043b000000000001042d000000000001042f000016a8002104210000000102000039000000000001042d0000000002000019000000000001042d000016ad002104230000000102000039000000000001042d0000000002000019000000000001042d000016af00000432000016b00001042e000016b1000104300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000001ffffffe000000000000000000000000000000000000000000000000000000000ffffffe0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff616c697a696e6700000000000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320696e69746908c379a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000000000000000000000002000000000000000000000000000000000000200000000000000000000000007f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498729563fd224b9a644af3cb647d3cb666ff093c97c2e7f1c6cefe51ee5bea587e00000002000000000000000000000000000000800000010000000000000000000000000000000000000000000000000000000000000000000000000092a85fdd00000000000000000000000000000000000000000000000000000000b4bd9e2600000000000000000000000000000000000000000000000000000000e2982c2000000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000ffa1ad7400000000000000000000000000000000000000000000000000000000e2982c2100000000000000000000000000000000000000000000000000000000e6064de300000000000000000000000000000000000000000000000000000000c32429db00000000000000000000000000000000000000000000000000000000c32429dc00000000000000000000000000000000000000000000000000000000c3b88b4200000000000000000000000000000000000000000000000000000000b4bd9e2700000000000000000000000000000000000000000000000000000000be8423de00000000000000000000000000000000000000000000000000000000a6b84e3000000000000000000000000000000000000000000000000000000000ab803a7500000000000000000000000000000000000000000000000000000000ab803a7600000000000000000000000000000000000000000000000000000000b421520700000000000000000000000000000000000000000000000000000000a6b84e3100000000000000000000000000000000000000000000000000000000a9e0c9c30000000000000000000000000000000000000000000000000000000092a85fde000000000000000000000000000000000000000000000000000000009d326b3d00000000000000000000000000000000000000000000000000000000a04748f6000000000000000000000000000000000000000000000000000000006ec0bc4900000000000000000000000000000000000000000000000000000000826b90f1000000000000000000000000000000000000000000000000000000008dbc8342000000000000000000000000000000000000000000000000000000008dbc83430000000000000000000000000000000000000000000000000000000090ced42100000000000000000000000000000000000000000000000000000000826b90f2000000000000000000000000000000000000000000000000000000008da5cb5b000000000000000000000000000000000000000000000000000000006ec0bc4a00000000000000000000000000000000000000000000000000000000715018a60000000000000000000000000000000000000000000000000000000079502c550000000000000000000000000000000000000000000000000000000031ab05170000000000000000000000000000000000000000000000000000000047535d7a0000000000000000000000000000000000000000000000000000000047535d7b000000000000000000000000000000000000000000000000000000004cc233a80000000000000000000000000000000000000000000000000000000031ab05180000000000000000000000000000000000000000000000000000000031b3eb94000000000000000000000000000000000000000000000000000000001a203c4f000000000000000000000000000000000000000000000000000000001be19560000000000000000000000000000000000000000000000000000000002ddbd13a342e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000010000000100000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084000000e000000000000000000000000000000000000000000000000000000020000000e00000000000000000e3a9db1a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000e0000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000e00000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c726561ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000fffffffffffffedf5320ad99a619a90804cd2efe3a5cf0ac1ac5c41ad9ff2c61cf699efdad771096796b89b91644bc98cd93958e4c9038275d622183e25ac5af08cc6b5d95539132020000020000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000f48657000000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000ffffffffffffff7b010000b735cd61ab80da8bff6c846bccb40a7fbdf3d63d2f1d523fdcdced5a099c4d535bdea7cd8a978f128b93471df48c7dbab89d703809115bdc118c235bfd0200000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000008129fc1c0000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000746f6b656e20616e64206f7261636c65206c656e6774687320213d00000000000000000000000000000000000000000000000064000000000000000000000000746f6b656e20616e6420646563696d616c73206c656e6774687320213d0000008000000000000000000000000000000000000000000000000000000000000000acdf526659e656f7fb32d101c5a30f53e53a3be52600d39e309661025288ef6a58317c92fcd4d409d481df68571f5927514cabfa52ead8e1692c4fe775e2f905a7ce836d032b2bf62b7e2097a8e0a6d8aeb35405ad15271e96d3b0188a1d06fb58317c92fcd4d409d481df68571f5927514cabfa52ead8e1692c4fe775e2f9040200000000000000000000000000000000000000000000000000000000000000b08510a4a112663ff701fcf43686a47fd456cb1a471dd63d662b73612cf700b34e487b71000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff9f02000000000000000000000000000000000000400000000000000000000000000200000000000000000000000000000000000080000000000000000000000000e9b592b561a1c20f7ad5ea0af6d767fa74bc9fc3f038c70f1718f191517ed94842cbb15ccdc3cad6266b0e7a08c0454b23bf29dc2df74b6f3c209e9336465bd180b41246c05cbb406f874e82aa2faf7db11bba9792fe09929e56ef1eee2c2da38be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0746f6b656e206f7261636c65203d3d20300000000000000000000000000000007061796d656e7420746f6b656e203d3d203000000000000000000000000000006e6174697665206f7261636c65203d3d203000000000000000000000000000006e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f742069000000000000000000000000000000000000000000000000ffffffffffffff1f938b5f3299a1f3b18e458564efbb950733226014eece26fae19012d850b48d83310ab089e4439a4c15d089f94afb7896ff553aecb10793d0ab882de59d99a32e02000002000000000000000000000000000000440000000000000000000000006afb5ced000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000e9ed68b00000000000000000000000000000000000000000000000000000000b31c8781000000000000000000000000000000000000000000000000000000004ccb20c00000000000000000000000000000000000000000000000000000000023b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff5f75a85e7be0265abefef113ee168a0d751385a985c3a37920ae97ae192d2eadb4696e76616c6964207061796d656e7420746f6b656e00000000000000000000009cc7f708afc65944829bd487b90b72536b1951864fbfc14e125fc972a6507f395472616e73666572206661696c65642e000000000000000000000000000000003b381fdfc0e2729a70e8b26ae2397e9014f703a8235b557f5581c4ed47280fd24d75737420627579207769746820616e20454f4100000000000000000000000073616c6520686173206e6f74207374617274656420796574000000000000000073616c652068617320656e64656400000000000000000000000000000000000073616c6520627579206c696d69742072656163686564000000000000000000006e6f7420796f7572207475726e20796574000000000000000000000000000000666565207061796d656e742062656c6f77206d696e696d756d000000000000005265656e7472616e637947756172643a207265656e7472616e742063616c6c006e6174697665207061796d656e74732064697361626c656400000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffdff73616c65206973206f7665723a2063616e6e6f74207570617465000000000000746172740000000000000000000000000000000000000000000000000000000065646974696e672073616c654d6178696d756d2061667465722073616c65207300000000000000000000000000000000000000000000000000000000f48657010000000000000000000000000000000000000000000000000000000000093a816178517565756554696d6500000000000000000000000000000000000000000073616c65206d757374206265206f70656e20666f72206174206c65617374206d4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572acdf526659e656f7fb32d101c5a30f53e53a3be52600d39e309661025288ef69f19e3240beb82d0dfa0a35ed50201f0ac38ea92b9b29450527293aa8ccd0979b70757263686173654d696e696d756d203e20757365724d6178696d756d000000757365724d6178696d756d203e2073616c654d6178696d756d00000000000000757365724d6178696d756d203d3d20300000000000000000000000000000000073616c654d6178696d756d203d3d203000000000000000000000000000000000726563697069656e74203d3d20616464726573732830290000000000000000006d61782071756575652074696d65203e20363034383030202831207765656b29656e64203e203431303234343438303020284a616e20312032313030290000007374617274203e203431303234343438303020284a616e20312032313030290051cff8d900000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000064000000e000000000000000000200000000000000000000000000000000000020000000e00000000000000000bfbef9c12fc2cf7df9f0b7679d5863e31a093e9c2c8d5dd9de1ddca31a0748284469737472696275746f72203d3d20616464726573732830290000000000000070a0823100000000000000000000000000000000000000000000000000000000a9059cbb00000000000000000000000000000000000000000000000000000000f4a44a7f605c4971a27bcecb448108e6328b7fad34fab5bff4f69377294b826dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff000000000000000000000000000000000000000000000000ffffffffffffffa0feaf968c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffff7374616c652070726963652064756520746f2068656172746265617400000000726f756e64206e6f7420636f6d706c6574650000000000000000000000000000616e73776572203d3d20300000000000000000000000000000000000000000006e656761746976652070726963650000000000000000000000000000000000006563697069656e74206d61792068617665207265766572746564000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c2072416464726573733a20696e73756666696369656e742062616c616e6365000000707572636861736520756e646572206d696e696d756d00000000000000000000707572636861736520657863656564732073616c65206c696d697400000000007075726368617365206578636565647320796f7572206c696d69740000000000000000000000000000000000000000000000000000000000ffffffffffffffc05361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656400000000000000000000000000000000000000000000000000000003ffffffe06f742073756363656564000000000000000000000000000000000000000000005361666545524332303a204552433230206f7065726174696f6e20646964206e416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000f340fa0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff8019457468657265756d205369676e6564204d6573736167653a0a333200000000020000000000000000000000000000000000003c00000000000000000000000045434453413a20696e76616c6964207369676e6174757265206c656e677468007fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a00000000000000000000000000000000000000080000000000000000000000000616363657373207369676e617475726520696e76616c69640000000000000000616363657373207369676e61747572652065787069726564000000000000000045434453413a20696e76616c6964207369676e61747572650000000000000000756500000000000000000000000000000000000000000000000000000000000045434453413a20696e76616c6964207369676e6174757265202773272076616c0200000200000000000000000000000000000000000000000000000000000000570e0aebf0b17686d0af2f672e030e023ba4003b778bb2cb9123441879c6e8d8", + "entries": [ + { + "constructorArgs": [ + "0xEd852E0b2B936E95dcbE43244d3F55525A79d4F7" + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [ + "0x000200000000000200050000000000020000006003100270000000860330019700010000003103550000008004000039000000400040043f0000000100200190000000270000c13d000000040030008c0000015e0000413d000000000201043b000000e002200270000000880020009c0000002f0000a13d000000890020009c000000530000213d0000008c0020009c000000cb0000613d0000008d0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d000000000010043f0000006501000039000000200010043f0000000001000019021501fa0000040f000000000101041a000000800010043f0000009b01000041000002160001042e0000000001000416000000000001004b0000015e0000c13d0000002001000039000001000010044300000120000004430000008701000041000002160001042e0000008e0020009c000000880000613d0000008f0020009c000000f10000613d000000900020009c0000015e0000c13d0000000001000416000000000001004b0000015e0000c13d0000000003000415000000050330008a0000000503300210000000000200041a0000ff00012001900000010f0000c13d0000000003000415000000040330008a0000000503300210000000ff002001900000010f0000c13d000000a10120019700000101011001bf0000000002000019000000000010041b0000ff0000100190000001320000c13d000000400100043d0000006402100039000000a60300004100000000003204350000004402100039000000a703000041000000000032043500000024021000390000002b030000390000016d0000013d0000008a0020009c000000d40000613d0000008b0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000301041a0000000002000416000000000032001a000001de0000413d0000000003320019000000000031041b000000400100043d0000000000210435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000020300003900000094040000410000000305000029000001590000013d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000201041a000200000002001d000000000001041b000000aa010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800a02000039021502100000040f0000000100200190000001600000613d000000000101043b0000000203000029000000000031004b0000017b0000813d000000400100043d0000004402100039000000b003000041000000000032043500000024021000390000001d03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000b1011001c700000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000101041a0000009101100197000000800010043f0000009b01000041000002160001042e000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009100100198000001780000c13d0000009701000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000009801000041000000c40010043f0000009901000041000000e40010043f0000009a0100004100000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000201041a00000091052001970000000003000411000000000035004b000001060000c13d000000a202200197000000000021041b0000000001000414000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410000000006000019000001590000013d0000009701000041000000800010043f0000002001000039000000840010043f000000a40010043f000000a801000041000000c40010043f000000a9010000410000021700010430000300000003001d000100000001001d000200000002001d0000009c010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800202000039021502100000040f0000000100200190000001600000613d000000000101043b000000000001004b000001610000c13d0000000202000029000000ff0120018f000000010010008c00000003010000290000000501100270000000000100003f000000010100603f000001640000c13d000000010000006b000000430000613d000000b201200197000000010200003900000001011001bf000000000010041b0000ff0000100190000000490000613d000300000002001d000000000100041100000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f00000001002001900000015e0000613d000000030000006b0000015c0000c13d000000000200041a000000b301200197000000000010041b0000000103000039000000400100043d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000a5040000410215020b0000040f00000001002001900000015e0000613d0000000001000019000002160001042e00000000010000190000021700010430000000000001042f00000003010000290000000501100270000000000100003f000000400100043d00000064021000390000009e03000041000000000032043500000044021000390000009f03000041000000000032043500000024021000390000002e03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000a0011001c70000021700010430021501e40000040f0000000001000019000002160001042e00000000010004140000000302000029000000040020008c000001820000c13d00000001020000390000000001000031000001930000013d000000860010009c0000008601008041000000c001100210000000000003004b000001890000c13d00000003020000290000018d0000013d000000a3011001c70000800902000039000000030400002900000000050000190215020b0000040f000000020300002900010000000103550000006001100270000000860010019d0000008601100197000000000001004b000001a60000c13d000000400100043d0000000100200190000001ae0000613d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d020000390000000203000039000000af04000041000000860000013d000000ab0010009c000001b70000413d0000009501000041000000000010043f0000004101000039000000040010043f000000960100004100000217000104300000006402100039000000ad0300004100000000003204350000004402100039000000ae03000041000000000032043500000024021000390000003a030000390000016d0000013d0000001f04100039000000b4044001970000003f04400039000000b405400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000000ac0050009c000001a80000213d0000000100600190000001a80000c13d000000400050043f0000000006140436000000b4091001980000001f0410018f00000000019600190000000105000367000001d00000613d000000000705034f000000007807043c0000000006860436000000000016004b000001cc0000c13d000000000004004b000001950000613d000000000695034f0000000304400210000000000501043300000000054501cf000000000545022f000000000606043b0000010004400089000000000646022f00000000044601cf000000000454019f0000000000410435000001950000013d0000009501000041000000000010043f0000001101000039000000040010043f0000009601000041000002170001043000000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f0000000100200190000001f70000613d000000000001042d00000000010000190000021700010430000000000001042f0000000002000414000000860020009c0000008602008041000000c002200210000000860010009c00000086010080410000004001100210000000000121019f00000092011001c70000801002000039021502100000040f0000000100200190000002090000613d000000000101043b000000000001042d000000000100001900000217000104300000020e002104210000000102000039000000000001042d0000000002000019000000000001042d00000213002104230000000102000039000000000001042d0000000002000019000000000001042d0000021500000432000002160001042e000002170001043000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000f340fa01000000000000000000000000000000000000000000000000000000008da5cb5b00000000000000000000000000000000000000000000000000000000e3a9db1a0000000000000000000000000000000000000000000000000000000051cff8d900000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000008129fc1c000000000000000000000000ffffffffffffffffffffffffffffffffffffffff020000000000000000000000000000000000004000000000000000000000000002000000000000000000000000000000000000200000000000000000000000002da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c44e487b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000008c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000080000000000000000000000000000000000000000000000000000000200000008000000000000000001806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c7265610000000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e07f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024986e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f7420694f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657200000000000000000000000000000000000000640000008000000000000000009cc7f708afc65944829bd487b90b72536b1951864fbfc14e125fc972a6507f390000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff6563697069656e74206d61792068617665207265766572746564000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c20727084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5416464726573733a20696e73756666696369656e742062616c616e63650000000000000000000000000000000000000000000064000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000018b532e372f3b4020a6dd5ffb28e63fce9dab8e991740d8c60724ec4c1e2ce5" + ], + "address": "0x5D472F0509801Cd295b1636D6Cf38B8643F3C78D", + "txHash": "0x1a12e82a190edb141ec6491c1142ee1620767ee80980c1d6dfb9e9542f79028e" + } + ] +} diff --git a/zksync-ts/deployments-zk/zkSyncMainnet/contracts/sale/v4/FlatPriceSaleFactory.sol/FlatPriceSaleFactory_v_4_0.json b/zksync-ts/deployments-zk/zkSyncMainnet/contracts/sale/v4/FlatPriceSaleFactory.sol/FlatPriceSaleFactory_v_4_0.json new file mode 100644 index 00000000..2b9b00a4 --- /dev/null +++ b/zksync-ts/deployments-zk/zkSyncMainnet/contracts/sale/v4/FlatPriceSaleFactory.sol/FlatPriceSaleFactory_v_4_0.json @@ -0,0 +1,330 @@ +{ + "sourceName": "contracts/sale/v4/FlatPriceSaleFactory.sol", + "contractName": "FlatPriceSaleFactory_v_4_0", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract FlatPriceSale_v_4_0", + "name": "clone", + "type": "address" + }, + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "indexed": false, + "internalType": "struct Config", + "name": "config", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "string", + "name": "baseCurrency", + "type": "string" + }, + { + "indexed": false, + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "nativeOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nativeOracleHeartbeat", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "nativePaymentsEnabled", + "type": "bool" + } + ], + "name": "NewSale", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "internalType": "struct Config", + "name": "_config", + "type": "tuple" + }, + { + "internalType": "string", + "name": "_baseCurrency", + "type": "string" + }, + { + "internalType": "bool", + "name": "_nativePaymentsEnabled", + "type": "bool" + }, + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "_nativeTokenPriceOracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nativeTokenPriceOracleHeartbeat", + "type": "uint256" + }, + { + "internalType": "contract IERC20Upgradeable[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck[]", + "name": "oracles", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "oracleHeartbeats", + "type": "uint256[]" + }, + { + "internalType": "uint8[]", + "name": "decimals", + "type": "uint8[]" + } + ], + "name": "newSale", + "outputs": [ + { + "internalType": "contract FlatPriceSale_v_4_0", + "name": "sale", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "name": "upgradeFutureSales", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x0004000000000002001a0000000000020000006004100270000000f60340019700030000003103550002000000010355000000f60040019d0000000100200190000000b20000c13d0000008002000039000000400020043f000000040030008c000001770000413d000000000201043b000000e002200270000000fe0020009c000000f20000a13d000000ff0020009c0000010a0000213d000001020020009c000001240000613d000001030020009c000001770000c13d000001440030008c000001770000413d0000000002000416000000000002004b000001770000c13d0000000402100370000000000a02043b000000f900a0009c000001770000213d0000002402100370000000000902043b0000010d0090009c000001770000213d00000000029300490000010e0020009c000001770000213d000001240020008c000001770000413d0000004402100370000000000202043b0000010d0020009c000001770000213d0000002304200039000000000034004b000001770000813d0000000408200039000000000481034f000000000404043b0000010d0040009c000001770000213d00000000024200190000002402200039000000000032004b000001770000213d0000006402100370000000000602043b000000000006004b0000000002000039000000010200c039000000000026004b000001770000c13d0000008402100370000000000702043b000000f90070009c000001770000213d000000c402100370000000000202043b0000010d0020009c000001770000213d0000002304200039000000000034004b000001770000813d0000000404200039000000000441034f000000000504043b0000010d0050009c000001770000213d001a00240020003d00000005025002100000001a02200029000000000032004b000001770000213d000000e402100370000000000202043b0000010d0020009c000001770000213d0000002304200039000000000034004b000001770000813d0000000404200039000000000441034f000000000404043b001900000004001d0000010d0040009c000001770000213d001800240020003d000000190200002900000005022002100000001802200029000000000032004b000001770000213d0000010402100370000000000202043b0000010d0020009c000001770000213d0000002304200039000000000034004b000001770000813d0000000404200039000000000441034f000000000404043b001500000004001d0000010d0040009c000001770000213d000000240420003900000015020000290000000502200210001300000002001d001200000004001d0000000002420019000000000032004b000001770000213d0000012402100370000000000202043b0000010d0020009c000001770000213d0000002304200039000000000034004b000001770000813d0000000404200039000000000141034f000000000101043b001400000001001d0000010d0010009c000001770000213d001600240020003d000000140100002900000005011002100000001601100029000000000031004b000001770000213d000d0000000a001d001100000009001d000e00000008001d000f00000007001d001000000006001d001700000005001d0000000101000039000000000101041a0000010f02000041000000a40020043f000000f901100197000001040010043f00000000010004140000011002000041000000800020043f000000840000043f0000006002000039000000c40020043f0000002002000039000000e40020043f000000f60010009c000000f601008041000000c00110021000000111011001c7000080060200003903d403ca0000040f0000000100200190000001790000613d000000000101043b000000000001004b0000019d0000c13d000000030100036700000001020000310000017d0000013d0000000002000416000000000002004b000001770000c13d0000001f02300039000000f7022001970000008002200039000000400020043f0000001f0430018f000000f8053001980000008002500039000000c30000613d0000008006000039000000000701034f000000007807043c0000000006860436000000000026004b000000bf0000c13d000000000004004b000000d00000613d000000000151034f0000000304400210000000000502043300000000054501cf000000000545022f000000000101043b0000010004400089000000000141022f00000000014101cf000000000151019f0000000000120435000000200030008c000001770000413d000000800300043d000000f90030009c000001770000213d000000000100041a000000fa021001970000000006000411000000000262019f000000000020041b0000000002000414000000f905100197000000f60020009c000000f602008041000000c001200210000000fb011001c70000800d02000039001a00000003001d0000000303000039000000fc0400004103d403ca0000040f0000001a030000290000000100200190000001770000613d0000000101000039000000000201041a000000fa02200197000000000232019f000000000021041b000000200100003900000100001004430000012000000443000000fd01000041000003d50001042e000001040020009c0000011e0000613d000001050020009c000001480000613d000001060020009c000001770000c13d000000240030008c000001770000413d0000000002000416000000000002004b000001770000c13d0000000401100370000000000101043b000000f90010009c000001770000213d001a00000001001d03d403b30000040f0000000101000039000000000201041a000000fa022001970000001a022001af000000000021041b0000000001000019000003d50001042e000001000020009c0000012c0000613d000001010020009c000001770000c13d0000000001000416000000000001004b000001770000c13d000000c001000039000000400010043f0000000301000039000000800010043f0000010702000041000000a00020043f0000002003000039000000c00030043f000000e00010043f000001000020043f000001030000043f0000010801000041000003d50001042e0000000001000416000000000001004b000001770000c13d0000000101000039000000000101041a000001280000013d0000000001000416000000000001004b000001770000c13d000000000100041a000000f901100197000000800010043f0000011c01000041000003d50001042e000000240030008c000001770000413d0000000002000416000000000002004b000001770000c13d0000000401100370000000000601043b000000f90060009c000001770000213d000000000100041a000000f9021001970000000005000411000000000052004b000001600000c13d000000000006004b000001690000c13d0000010901000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000010a01000041000000c40010043f0000010b01000041000000e40010043f0000010c01000041000003d6000104300000000001000416000000000001004b000001770000c13d000000000100041a000000f9021001970000000005000411000000000052004b000001600000c13d000000fa01100197000000000010041b0000000001000414000000f60010009c000000f601008041000000c001100210000000fb011001c70000800d020000390000000303000039000000fc04000041000000000600001903d403ca0000040f0000000100200190000001770000613d0000000001000019000003d50001042e0000010901000041000000800010043f0000002001000039000000840010043f000000a40010043f0000011d01000041000000c40010043f0000011e01000041000003d600010430000000fa01100197000000000161019f000000000010041b0000000001000414000000f60010009c000000f601008041000000c001100210000000fb011001c70000800d020000390000000303000039000000fc0400004103d403ca0000040f00000001002001900000015e0000c13d0000000001000019000003d60001043000030000000103550000006002100270000100f60020019d000000f6022001970000011f052001980000001f0620018f000000400300043d0000000004530019000001880000613d000000000701034f0000000008030019000000007907043c0000000008980436000000000048004b000001840000c13d000000000006004b000001950000613d000000000151034f0000000305600210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f0000000000140435000000f60020009c000000f6020080410000006001200210000000f60030009c000000f6030080410000004002300210000000000112019f000003d6000104300000000102000039000000000402041a000000400200043d000000a00300003900000000053204360000001108000029000c00040080003d00000002030003670000000c06300360000000000606043b000000f90060009c000001770000213d000000a00720003900000000006704350000000c09000029000b00200090003d0000000b06300360000000000606043b000000c0072000390000000000670435000a00400090003d0000000a06300360000000000606043b000000e0072000390000000000670435000900600090003d0000000906300360000000000606043b00000100072000390000000000670435000800800090003d0000000806300360000000000606043b00000120072000390000000000670435000700a00090003d0000000706300360000000000606043b00000140072000390000000000670435000600c00090003d0000000606300360000000000606043b000001600720003900000000006704350000018006200039000500e00090003d0000000507300360000000000707043b0000000000760435000401000090003d000000040630036000000000070000310000000008870049000000230880008a000000000606043b0000011209600197000001120a800197000000000ba9013f0000000000a9004b00000000090000190000011209004041000000000086004b000000000800001900000112080080410000011200b0009c000000000908c019000000000009004b000001770000c13d0000000c08600029000000000683034f000000000606043b0000010d0060009c000001770000213d00000020088000390000000007670049000000000078004b000000000900001900000112090020410000011207700197000001120a800197000000000b7a013f00000000007a004b000000000700001900000112070040410000011200b0009c000000000709c019000000000007004b000001770000c13d000001a00720003900000120090000390000000000970435000001c0072000390000000000670435000000000983034f0000011f0a6001980000001f0b60018f000001e0072000390000000008a70019000002070000613d000000000c09034f000000000d07001900000000ce0c043c000000000ded043600000000008d004b000002030000c13d00000000000b004b000002140000613d0000000009a9034f000000030ab00210000000000b080433000000000bab01cf000000000bab022f000000000909043b000001000aa000890000000009a9022f0000000009a901cf0000000009b9019f0000000000980435000000000876001900000000000804350000001f066000390000011f066001970000000006760019000000000726004900000000007504350000000e07000029000000000573034f000200200070003d0000000209300360000000000705043b00000000067604360000011f0a7001980000001f0b70018f0000000008a600190000022b0000613d000000000509034f000000000c060019000000005d05043c000000000cdc043600000000008c004b000002270000c13d000000f90540019700000000000b004b000002390000613d0000000004a9034f0000000309b00210000000000a080433000000000a9a01cf000000000a9a022f000000000404043b0000010009900089000000000494022f00000000049401cf0000000004a4019f00000000004804350000000004670019000000000004043500000040042000390000000f080000290000000000840435000000a403300370000000000303043b000000800420003900000010080000290000000000840435000000600420003900000000003404350000001f037000390000011f0330019700000000042600490000000003340019000000f60030009c000000f6030080410000006003300210000000f60020009c000000f6020080410000004002200210000000000223019f0000000003000414000000f60030009c000000f603008041000000c003300210000000000223019f000000f906100197000000fb012001c70000800d0200003900000003030000390000011304000041000300000006001d03d403ca0000040f0000000100200190000001770000613d00000114010000410000000000100443000000030100002900000004001004430000000001000414000000f60010009c000000f601008041000000c00110021000000115011001c7000080020200003903d403cf0000040f00000001002001900000038a0000613d000000000101043b000000000001004b00000011040000290000000d03000029000001770000613d000000400500043d00000024015000390000014002000039000000000021043500000116010000410000000000150435000100000005001d0000000401500039000000000031043500000002010003670000000c02100360000000000202043b000000f90020009c000001770000213d0000000105000029000001440350003900000000002304350000000b02100360000000000202043b000001640350003900000000002304350000000a02100360000000000202043b000001840350003900000000002304350000000902100360000000000202043b000001a40350003900000000002304350000000802100360000000000202043b000001c40350003900000000002304350000000702100360000000000202043b000001e40350003900000000002304350000000602100360000000000202043b0000020403500039000000000023043500000005021003600000022403500039000000000202043b0000000000230435000000040210036000000000030000310000000004430049000000230440008a000000000202043b00000112052001970000011206400197000000000765013f000000000065004b00000000050000190000011205004041000000000042004b00000000040000190000011204008041000001120070009c000000000504c019000000000005004b000001770000c13d0000000c04200029000000000241034f000000000202043b0000010d0020009c000001770000213d00000020044000390000000003230049000000000034004b0000000005000019000001120500204100000112033001970000011206400197000000000736013f000000000036004b00000000030000190000011203004041000001120070009c000000000305c019000000000003004b000001770000c13d000000010800002900000244038000390000012005000039000000000053043500000264038000390000000000230435000000000541034f0000011f062001980000001f0720018f00000284038000390000000004630019000002d50000613d000000000805034f0000000009030019000000008a08043c0000000009a90436000000000049004b000002d10000c13d000000000007004b000002e20000613d000000000565034f0000000306700210000000000704043300000000076701cf000000000767022f000000000505043b0000010006600089000000000565022f00000000056501cf000000000575019f0000000000540435000000000432001900000000000404350000001f022000390000011f022001970000000104000029000000440440003900000280052000390000000000540435000000000232001900000002051003600000000e03100360000000000303043b00000000023204360000011f063001980000001f0730018f0000000004620019000002f90000613d000000000805034f0000000009020019000000008a08043c0000000009a90436000000000049004b000002f50000c13d000000000007004b000003060000613d000000000565034f0000000306700210000000000704043300000000076701cf000000000767022f000000000505043b0000010006600089000000000565022f00000000056501cf000000000575019f000000000054043500000000042300190000000000040435000000010600002900000084046000390000000f050000290000000000540435000000640460003900000010050000290000000000540435000000a404100370000000000404043b000000a40560003900000000004504350000001f033000390000011f0330019700000000022300190000000003620049000000040330008a000000c404600039000000000034043500000017030000290000000002320436000000000003004b0000032a0000613d00000000030000190000001a04100360000000000404043b000000f90040009c0000001705000029000001770000213d00000000024204360000001a04000029001a00200040003d0000000103300039000000000053004b0000031f0000413d00000001040000290000000003420049000000040330008a000000e404400039000000000034043500000019030000290000000002320436000000000003004b0000033e0000613d00000000030000190000001804100360000000000404043b000000f90040009c000001770000213d00000000024204360000001804000029001800200040003d0000000103300039000000190030006c000003340000413d00000001040000290000000003420049000000040330008a0000010404400039000000000034043500000015040000290000000003420436000001170040009c000001770000213d00000013050000290000001f0450018f000000000005004b000003510000613d00000012051003600000001306300029000000005705043c0000000003730436000000000063004b0000034d0000c13d000000000004004b0000001302200029000000010400002900000000034200490000001c03300039000001240440003900000000003404350000002003200039000000140400002900000000004304350000004002200039000000000004004b000003690000613d00000000030000190000001604100360000000000404043b000000ff0040008c000001770000213d00000000024204360000001604000029001600200040003d0000000103300039000000140030006c0000035f0000413d00000000010004140000000303000029000000040030008c000003810000613d00000001030000290000000002320049000000f60020009c000000f6020080410000006002200210000000f60030009c000000f6030080410000004003300210000000000232019f000000f60010009c000000f601008041000000c001100210000000000112019f000000030200002903d403ca0000040f0000006003100270000100f60030019d00030000000103550000000100200190000003940000613d0000000101000029000001180010009c0000038b0000413d0000011a01000041000000000010043f0000004101000039000000040010043f0000011b01000041000003d600010430000000000001042f0000000102000029000000400020043f00000003010000290000000000120435000000f60020009c000000f602008041000000400120021000000119011001c7000003d50001042e000000f6033001970000001f0530018f000000f806300198000000400200043d0000000004620019000003a00000613d000000000701034f0000000008020019000000007907043c0000000008980436000000000048004b0000039c0000c13d000000000005004b000003ad0000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000006001300210000000f60020009c000000f6020080410000004002200210000000000112019f000003d600010430000000000100041a000000f9011001970000000002000411000000000021004b000003b90000c13d000000000001042d000000400100043d00000044021000390000011d0300004100000000003204350000010902000041000000000021043500000024021000390000002003000039000000000032043500000004021000390000000000320435000000f60010009c000000f601008041000000400110021000000120011001c7000003d600010430000000000001042f000003cd002104210000000102000039000000000001042d0000000002000019000000000001042d000003d2002104230000000102000039000000000001042d0000000002000019000000000001042d000003d400000432000003d50001042e000003d600010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000001ffffffe000000000000000000000000000000000000000000000000000000000ffffffe0000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e00000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000ffa1ad74000000000000000000000000000000000000000000000000000000008da5cb5b00000000000000000000000000000000000000000000000000000000a7443405000000000000000000000000000000000000000000000000000000005c60da1b00000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000007bfce4df342e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000c0000000000000000008c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084000000800000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0100003900d100de6d0a86ddea4a8d758b1fb6c3ce5354f69ef1ee10db885d439c4d535bdea7cd8a978f128b93471df48c7dbab89d703809115bdc118c235bfd02000000000000000000000000000000000000a4000000800000000000000000800000000000000000000000000000000000000000000000000000000000000062480a5b22a42a66c986adaff1aa45bcd50564da47d78e0a13351b7f6f6d6c491806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000be8423de0000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000200000000000000000000000004e487b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000200000008000000000000000004f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65720000000000000000000000000000000000000064000000800000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ed0ed8a75f891e5245fa619dd2557c1c8875a5596e0ab44b55a11c837a0d8f65", + "entries": [ + { + "constructorArgs": [ + "0x5D472F0509801Cd295b1636D6Cf38B8643F3C78D" + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [ + "0x0002000000000002000100000000000200000060031002700000002e08300197000100000081035500000001002001900000004b0000c13d0000001f0380018f00000030048001980000008002400039000000110000613d0000008005000039000000000601034f000000006706043c0000000005750436000000000025004b0000000d0000c13d000100000008001d000000000003004b0000001f0000613d000000000141034f0000000303300210000000000402043300000000043401cf000000000434022f000000000101043b0000010003300089000000000131022f00000000013101cf000000000141019f00000000001204350000003301000041000000000010044300000000010004120000000400100443000000240000044300000000010004140000002e0010009c0000002e01008041000000c00110021000000034011001c7000080050200003900b200a80000040f0000000100200190000000700000613d000000000201043b0000000001000414000000040020008c000000710000c13d0000000103000367000000000100003100000036041001980000001f0510018f00000080024000390000003d0000613d0000008006000039000000000703034f000000007807043c0000000006860436000000000026004b000000390000c13d000000000005004b000000960000613d000000000343034f0000000304500210000000000502043300000000054501cf000000000545022f000000000303043b0000010004400089000000000343022f00000000034301cf000000000353019f0000000000320435000000960000013d0000000002000416000000000002004b0000006e0000c13d0000001f028000390000002f02200197000000a002200039000000400020043f0000001f0380018f0000003004800198000000a0024000390000005c0000613d000000a005000039000000000601034f000000006706043c0000000005750436000000000025004b000000580000c13d000000000003004b000000690000613d000000000141034f0000000303300210000000000402043300000000043401cf000000000434022f000000000101043b0000010003300089000000000131022f00000000013101cf000000000141019f0000000000120435000000200080008c0000006e0000413d000000a00100043d000000310010009c0000009b0000a13d0000000001000019000000b400010430000000000001042f000000010300002900000060033002100000002e0010009c0000002e01008041000000c001100210000000000131019f00000035011001c700b200ad0000040f000100000001035500000060031002700000001f0530018f0000002e0030019d00000030063001980000008004600039000000860000613d0000008007000039000000000801034f000000008908043c0000000007970436000000000047004b000000820000c13d000000000005004b000000930000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000002e013001970000000100200190000000a40000613d0000002e0010009c0000002e01008041000000600110021000000035011001c7000000b30001042e000000800010043f0000014000000443000001600010044300000020010000390000010000100443000000010100003900000120001004430000003201000041000000b30001042e000000600110021000000035011001c7000000b400010430000000000001042f000000ab002104230000000102000039000000000001042d0000000002000019000000000001042d000000b0002104250000000102000039000000000001042d0000000002000019000000000001042d000000b200000432000000b30001042e000000b40001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000001ffffffe000000000000000000000000000000000000000000000000000000000ffffffe0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000200000000000000000000000000000080000001000000000000000000310ab089e4439a4c15d089f94afb7896ff553aecb10793d0ab882de59d99a32e02000002000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000000000000800000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000f21e537fa1fe5f0bf8064aafff9a6eb5249e22e6f3d163f71bf692d71d9f7c2c" + ], + "address": "0xd14FE6459168bEFACDe8d9ee9305d9827Ab51709", + "txHash": "0xec02c95d90a76bbd24ffbffbcb95d1f2776cd2372a75d8998b23d1a5d99666db" + } + ] +} diff --git a/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/.chainId b/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/.chainId new file mode 100644 index 00000000..66792661 --- /dev/null +++ b/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/.chainId @@ -0,0 +1 @@ +0x12c \ No newline at end of file diff --git a/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/claim/FeeLevelJudgeStub.sol/FeeLevelJudgeStub.json b/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/claim/FeeLevelJudgeStub.sol/FeeLevelJudgeStub.json new file mode 100644 index 00000000..83aca651 --- /dev/null +++ b/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/claim/FeeLevelJudgeStub.sol/FeeLevelJudgeStub.json @@ -0,0 +1,49 @@ +{ + "sourceName": "contracts/claim/FeeLevelJudgeStub.sol", + "contractName": "FeeLevelJudgeStub", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_feeLevel", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "getFeeLevel", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x000000600310027000000012033001970000000100200190000000190000c13d0000008002000039000000400020043f000000040030008c000000390000413d000000000201043b0000001602200197000000170020009c000000390000c13d000000240030008c000000390000413d0000000002000416000000000002004b000000390000c13d0000000401100370000000000101043b000000180010009c000000390000213d000000000100041a000000800010043f0000001901000041000000430001042e0000000002000416000000000002004b000000390000c13d0000001f0230003900000013022001970000008002200039000000400020043f0000001f0430018f000000140530019800000080025000390000002a0000613d0000008006000039000000000701034f000000007807043c0000000006860436000000000026004b000000260000c13d000000000004004b000000370000613d000000000151034f0000000304400210000000000502043300000000054501cf000000000545022f000000000101043b0000010004400089000000000141022f00000000014101cf000000000151019f0000000000120435000000200030008c0000003b0000813d00000000010000190000004400010430000000800100043d000000000010041b0000002001000039000001000010044300000120000004430000001501000041000000430001042e0000004200000432000000430001042e000000440001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000001ffffffe000000000000000000000000000000000000000000000000000000000ffffffe00000000200000000000000000000000000000040000001000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000b31c878100000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000020000000800000000000000000a9535a0b118c36ffd409886b497bf32212916d1241d3b06aab028a13e8c18e17", + "entries": [ + { + "constructorArgs": [ + 100 + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [], + "address": "0x83b458810Cd1E08e3571948Dc34358c26b72F7Cf", + "txHash": "0xb2c4801cb4ab22a662f80da26a8d0a634b8ca79064a4fbf0958cbade4671e13e" + } + ] +} diff --git a/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/config/NetworkConfig.sol/NetworkConfig.json b/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/config/NetworkConfig.sol/NetworkConfig.json new file mode 100644 index 00000000..1fc947d8 --- /dev/null +++ b/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/config/NetworkConfig.sol/NetworkConfig.json @@ -0,0 +1,245 @@ +{ + "sourceName": "contracts/config/NetworkConfig.sol", + "contractName": "NetworkConfig", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "accessAuthorityAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeRecipient", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAccessAuthorityAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getFeeRecipient", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNativeTokenPriceOracleAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNativeTokenPriceOracleHeartbeat", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStakingAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "_feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "_stakingAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_nativeTokenPriceOracleAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nativeTokenPriceOracleHeartbeat", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_accessAuthorityAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "nativeTokenPriceOracleAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeTokenPriceOracleHeartbeat", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakingAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x0001000000000002000900000000000200000000000103550000008003000039000000400030043f0000000100200190000000680000c13d00000060021002700000006303200197000000040030008c000000d40000413d000000000201043b000000e002200270000000650020009c000000700000213d0000006f0020009c000000790000a13d000000700020009c0000008f0000213d000000730020009c0000007f0000613d000000740020009c000000d40000c13d000000a40030008c000000d40000413d0000000002000416000000000002004b000000d40000c13d0000000402100370000000000402043b000000780040009c000000d40000213d0000002402100370000000000502043b000000780050009c000000d40000213d0000004402100370000000000602043b000000780060009c000000d40000213d0000008401100370000000000701043b000000780070009c000000d40000213d0000000003000415000000090330008a0000000503300210000000000200041a0000ff0001200190000000f60000c13d0000000003000415000000080330008a0000000503300210000000ff00200190000000f60000c13d000000880120019700000101011001bf0000000008000019000000000010041b0000006602000039000000000302041a00000089003001980000012a0000c13d000700000008001d00000078044001970000007805500197000000780660019700000078077001970000006508000039000000000908041a0000008009900197000000000449019f000000000048041b0000008c03300197000000000335019f0000008d033001c7000000000032041b0000006702000039000000000302041a0000008003300197000000000363019f000000000032041b00000064020000390000000002200367000000000202043b0000006803000039000000000023041b0000006902000039000000000302041a0000008003300197000000000373019f000000000032041b0000ff00001001900000013e0000c13d000000400100043d00000064021000390000009003000041000000000032043500000044021000390000009103000041000000000032043500000024021000390000002b03000039000001330000013d0000000001000416000000000001004b000000d40000c13d0000002001000039000001000010044300000120000004430000006401000041000001890001042e000000660020009c000000840000a13d000000670020009c000000ac0000213d0000006a0020009c000000b80000613d0000006b0020009c000000bd0000613d000000d40000013d000000750020009c000000b80000613d000000760020009c0000008a0000613d000000770020009c000000d40000c13d0000000001000416000000000001004b000000d40000c13d0000006501000039000000c60000013d0000006c0020009c000000c20000613d0000006d0020009c000000b00000613d0000006e0020009c000000d40000c13d0000000001000416000000000001004b000000d40000c13d0000006701000039000000c60000013d000000710020009c000000bd0000613d000000720020009c000000d40000c13d0000000001000416000000000001004b000000d40000c13d0000003301000039000000000201041a00000078052001970000000003000411000000000035004b000000ea0000c13d0000008002200197000000000021041b0000000001000414000000630010009c0000006301008041000000c00110021000000081011001c70000800d020000390000000303000039000000820400004100000000060000190188017e0000040f0000000100200190000000d40000613d0000000001000019000001890001042e000000680020009c000000cb0000613d000000690020009c000000d40000c13d0000000001000416000000000001004b000000d40000c13d0000006801000039000000000101041a000000800010043f0000007d01000041000001890001042e0000000001000416000000000001004b000000d40000c13d0000006601000039000000c60000013d0000000001000416000000000001004b000000d40000c13d0000006901000039000000c60000013d0000000001000416000000000001004b000000d40000c13d0000003301000039000000000101041a0000007801100197000000800010043f0000007d01000041000001890001042e000000240030008c000000d40000413d0000000002000416000000000002004b000000d40000c13d0000000401100370000000000101043b000000780010009c000000d60000a13d00000000010000190000018a000104300000003302000039000000000202041a00000078022001970000000003000411000000000032004b000000ea0000c13d0000007800100198000000f30000c13d0000007901000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000007a01000041000000c40010043f0000007b01000041000000e40010043f0000007c010000410000018a000104300000007901000041000000800010043f0000002001000039000000840010043f000000a40010043f0000007e01000041000000c40010043f0000007f010000410000018a00010430018801680000040f0000000001000019000001890001042e000700000003001d000100000001001d000600000002001d000200000007001d000300000006001d000400000005001d000500000004001d00000083010000410000000000100443000000000100041000000004001004430000000001000414000000630010009c0000006301008041000000c00110021000000084011001c70000800202000039018801830000040f00000001002001900000011f0000613d000000000101043b000000000001004b000001200000c13d0000000602000029000000ff0120018f000000010010008c00000007010000290000000501100270000000000100003f000000010100603f000001230000c13d000000010000006b0000000504000029000000040500002900000003060000290000000207000029000000370000613d0000009201200197000000010800003900000001011001bf0000003a0000013d000000000001042f00000007010000290000000501100270000000000100003f000000400100043d00000064021000390000008503000041000000000032043500000044021000390000008603000041000001300000013d000000400100043d00000064021000390000008a03000041000000000032043500000044021000390000008b03000041000000000032043500000024021000390000002e03000039000000000032043500000079020000410000000000210435000000040210003900000020030000390000000000320435000000630010009c0000006301008041000000400110021000000087011001c70000018a00010430000000000100041100000078061001970000003301000039000000000201041a0000008003200197000000000363019f000000000031041b00000000010004140000007805200197000000630010009c0000006301008041000000c00110021000000081011001c70000800d02000039000000030300003900000082040000410188017e0000040f0000000100200190000000d40000613d000000070000006b000000aa0000c13d000000000200041a0000009301200197000000000010041b0000000103000039000000400100043d0000000000310435000000630010009c000000630100804100000040011002100000000002000414000000630020009c0000006302008041000000c002200210000000000112019f0000008e011001c70000800d020000390000008f040000410188017e0000040f0000000100200190000000aa0000c13d000000d40000013d00000078061001970000003301000039000000000201041a0000008003200197000000000363019f000000000031041b00000000010004140000007805200197000000630010009c0000006301008041000000c00110021000000081011001c70000800d02000039000000030300003900000082040000410188017e0000040f00000001002001900000017b0000613d000000000001042d00000000010000190000018a00010430000000000001042f00000181002104210000000102000039000000000001042d0000000002000019000000000001042d00000186002104230000000102000039000000000001042d0000000002000019000000000001042d0000018800000432000001890001042e0000018a00010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000d7b4be2300000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000f5b1a3ac00000000000000000000000000000000000000000000000000000000d7b4be2400000000000000000000000000000000000000000000000000000000e0b626d7000000000000000000000000000000000000000000000000000000008da5cb5b000000000000000000000000000000000000000000000000000000009d326b3d00000000000000000000000000000000000000000000000000000000bbcda115000000000000000000000000000000000000000000000000000000004ccb20bf000000000000000000000000000000000000000000000000000000006afb5cec000000000000000000000000000000000000000000000000000000006afb5ced00000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000004ccb20c000000000000000000000000000000000000000000000000000000000530b97a4000000000000000000000000000000000000000000000000000000000e9ed68b00000000000000000000000000000000000000000000000000000000163f21bd0000000000000000000000000000000000000000000000000000000046904840000000000000000000000000ffffffffffffffffffffffffffffffffffffffff08c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000080000000000000000000000000000000000000000000000000000000200000008000000000000000004f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65720000000000000000000000000000000000000064000000800000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e01806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c7265610000000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000ff0000000000000000000000000000000000000000656e20696e697469616c697a6564000000000000000000000000000000000000436f6e747261637420696e7374616e63652068617320616c7265616479206265ffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000002000000000000000000000000000000000000200000000000000000000000007f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024986e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f742069ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffe3eb485ef07ba86a5e060b9f1b271cafe6036f12a16dfc3be0de6e2f8f6de418", + "entries": [ + { + "constructorArgs": [], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [], + "address": "0x034F9B8CE83901EB22a1589072D7406f379669F1", + "txHash": "0x285a31dc321e36473a941b22e1658df1dac0f20e768a7fbda0ccf3622d354810" + } + ] +} diff --git a/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/sale/v4/FlatPriceSale.sol/FlatPriceSale_v_4_0.json b/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/sale/v4/FlatPriceSale.sol/FlatPriceSale_v_4_0.json new file mode 100644 index 00000000..0b271c71 --- /dev/null +++ b/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/sale/v4/FlatPriceSale.sol/FlatPriceSale_v_4_0.json @@ -0,0 +1,1092 @@ +{ + "sourceName": "contracts/sale/v4/FlatPriceSale.sol", + "contractName": "FlatPriceSale_v_4_0", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_networkConfig", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "baseCurrencyValue", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenValue", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "protocolTokenFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "platformTokenFee", + "type": "uint256" + } + ], + "name": "Buy", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract INetworkConfig", + "name": "networkConfig", + "type": "address" + } + ], + "name": "ImplementationConstructor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "indexed": false, + "internalType": "struct Config", + "name": "config", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "string", + "name": "baseCurrency", + "type": "string" + }, + { + "indexed": false, + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "nativeOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "nativePaymentsEnabled", + "type": "bool" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "distributor", + "type": "address" + } + ], + "name": "RegisterDistributor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + } + ], + "indexed": false, + "internalType": "struct PaymentTokenInfo", + "name": "paymentTokenInfo", + "type": "tuple" + } + ], + "name": "SetPaymentTokenInfo", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SweepNative", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "SweepToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "indexed": false, + "internalType": "struct Config", + "name": "config", + "type": "tuple" + } + ], + "name": "Update", + "type": "event" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseCurrency", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "userLimit", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "expiresAt", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "address payable", + "name": "platformFlatRateFeeRecipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "platformFlatRateFeeAmount", + "type": "uint256" + } + ], + "name": "buyWithNative", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "quantity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userLimit", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "expiresAt", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "address payable", + "name": "platformFlatRateFeeRecipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "platformFlatRateFeeAmount", + "type": "uint256" + } + ], + "name": "buyWithToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "buyerTotal", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "config", + "outputs": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "generatePseudorandomValue", + "outputs": [ + { + "internalType": "uint160", + "name": "", + "type": "uint160" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "buyer", + "type": "address" + } + ], + "name": "getFairQueueTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + } + ], + "name": "getOraclePrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + } + ], + "name": "getPaymentToken", + "outputs": [ + { + "components": [ + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + } + ], + "internalType": "struct PaymentTokenInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "internalType": "struct Config", + "name": "_config", + "type": "tuple" + }, + { + "internalType": "string", + "name": "_baseCurrency", + "type": "string" + }, + { + "internalType": "bool", + "name": "_nativePaymentsEnabled", + "type": "bool" + }, + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "_nativeTokenPriceOracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nativeTokenPriceOracleHeartbeat", + "type": "uint256" + }, + { + "internalType": "contract IERC20Upgradeable[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck[]", + "name": "oracles", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "oracleHeartbeats", + "type": "uint256[]" + }, + { + "internalType": "uint8[]", + "name": "decimals", + "type": "uint8[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isOpen", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isOver", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "metrics", + "outputs": [ + { + "internalType": "uint256", + "name": "purchaseCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "buyerCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseTotal", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeTokenPriceOracle", + "outputs": [ + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nativeTokenPriceOracleHeartbeat", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "networkConfig", + "outputs": [ + { + "internalType": "contract INetworkConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "", + "type": "address" + } + ], + "name": "paymentTokens", + "outputs": [ + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "decimals", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dest", + "type": "address" + } + ], + "name": "payments", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_distributor", + "type": "address" + } + ], + "name": "registerDistributor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sweepNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenQuantity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tokenDecimals", + "type": "uint256" + }, + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "heartbeat", + "type": "uint256" + } + ], + "name": "tokensToBaseCurrency", + "outputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "total", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "internalType": "struct Config", + "name": "_config", + "type": "tuple" + } + ], + "name": "update", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "payee", + "type": "address" + } + ], + "name": "withdrawPayments", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x000400000000000200130000000000020000006004100270000005ad0340019700030000003103550002000000010355000005ad0040019d0000000100200190000000f00000c13d000000e002000039000000400020043f000000040030008c000001130000413d000000c00000043f000000000201043b000000e002200270000005b90020009c000001150000a13d000005ba0020009c000001860000a13d000005bb0020009c000001990000213d000005c10020009c000002b10000213d000005c40020009c000004830000613d000005c50020009c000001130000c13d000001440030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000402100370000000000202043b000005b00020009c000001130000213d000000800020043f0000002402100370000000000202043b000005ed0020009c000001130000213d0000000002230049000005ee0020009c000001130000213d000001240020008c000001130000413d0000004402100370000000000202043b000005ed0020009c000001130000213d0000002304200039000000000034004b000001130000813d0000000404200039000000000441034f000000000404043b000005ed0040009c000001130000213d00000000024200190000002402200039000000000032004b000001130000213d0000006402100370000000000402043b000000000004004b0000000002000039000000010200c039000f00000004001d000000000024004b000001130000c13d0000008402100370000000000202043b000e00000002001d000005b00020009c000001130000213d000000c402100370000000000202043b000005ed0020009c000001130000213d0000002304200039000000000034004b000001130000813d0000000404200039000000000441034f000000000404043b000d00000004001d000005ed0040009c000001130000213d000c00240020003d0000000d0200002900000005022002100000000c02200029000000000032004b000001130000213d000000e402100370000000000202043b000005ed0020009c000001130000213d0000002304200039000000000034004b000001130000813d0000000404200039000000000441034f000000000404043b000b00000004001d000005ed0040009c000001130000213d000a00240020003d0000000b0200002900000005022002100000000a02200029000000000032004b000001130000213d0000010402100370000000000202043b000005ed0020009c000001130000213d0000002304200039000000000034004b000001130000813d0000000404200039000000000441034f000000000404043b000900000004001d000005ed0040009c000001130000213d000800240020003d000000090200002900000005022002100000000802200029000000000032004b000001130000213d0000012402100370000000000202043b000005ed0020009c000001130000213d0000002304200039000000000034004b000001130000813d0000000404200039000000000141034f000000000101043b000700000001001d000005ed0010009c000001130000213d000500240020003d000000070100002900000005011002100000000501100029000000000031004b000001130000213d000000000100041a000600000001001d0000ff000010019000000000010000390000000101006039000000a00010043f000009e10000c13d0000000001000415000000100110008a00040005001002180000000601000029000000ff00100190001000000000003d001000010000603d0000000001000019000009e50000c13d0000000602000029000005f30220019700000101022001bf000000000021041b000000400100043d000005f40010009c0000076a0000213d0000012002100039000000400020043f000000d002000039000000000202041a000005b0022001970000000002210436000000d103000039000000000303041a0000000000320435000000d202000039000000000202041a0000004003100039000400000003001d0000000000230435000000d302000039000000000202041a00000060031000390000000000230435000000d402000039000000000202041a00000080031000390000000000230435000000d502000039000000000202041a000000a003100039000600000003001d0000000000230435000000d602000039000000000302041a000000c0021000390000000000320435000000e003100039000000d704000039000000000404041a0000000000430435000000d803000039000000000603041a000000010760019000000001046002700000007f0440618f0000001f0040008c00000000030000390000000103002039000000000336013f00000001003001900000047d0000c13d000000400300043d0000000005430436000000000007004b00000b180000613d000000d806000039000000000060043f000000000004004b000000000600001900000b1d0000613d000005f50700004100000000060000190000000008560019000000000907041a000000000098043500000001077000390000002006600039000000000046004b000000e80000413d00000b1d0000013d0000000002000416000000000002004b000001130000c13d0000001f02300039000005ae02200197000000a002200039000000400020043f0000001f0430018f000005af05300198000000a002500039000001010000613d000000a006000039000000000701034f000000007807043c0000000006860436000000000026004b000000fd0000c13d000000000004004b0000010e0000613d000000000151034f0000000304400210000000000502043300000000054501cf000000000545022f000000000101043b0000010004400089000000000141022f00000000014101cf000000000151019f0000000000120435000000200030008c000001130000413d000000a00300043d000005b00030009c000001410000a13d0000000001000019000016b100010430000005cf0020009c000001790000213d000005d90020009c000001b90000a13d000005da0020009c0000020b0000213d000005dd0020009c000002e80000613d000005de0020009c000001130000c13d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000f00000001001d000005b00010009c000001130000213d0000009701000039000000000101041a000005ef020000410000000000200443000005b001100197000e00000001001d00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000000001004b000006690000c13d000000c00100043d000005ad0010009c000005ad01008041000005e4011000d1000016b100010430000000000100041a0000ff0000100190000001a50000c13d000000ff0210018f000000ff0020008c0000015d0000613d000000ff011001bf000000000010041b000000ff01000039000000400200043d0000000000120435000005ad0020009c000005ad0200804100000040012002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f000005b5011001c70000800d02000039000f00000003001d0000000103000039000005b60400004116af16a50000040f0000000f030000290000000100200190000001130000613d000005b001300197000000800010043f000000400200043d0000000000120435000005ad0020009c000005ad0200804100000040012002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f000005b5011001c70000800d020000390000000103000039000005b70400004116af16a50000040f0000000100200190000001130000613d000000800100043d000001400000044300000160001004430000002001000039000001000010044300000001010000390000012000100443000005b801000041000016b00001042e000005d00020009c000001c40000a13d000005d10020009c0000021f0000213d000005d40020009c000003030000613d000005d50020009c000001130000c13d0000000001000416000000000001004b000001130000c13d0000006501000039000001a30000013d000005c60020009c000001f70000a13d000005c70020009c000002340000213d000005ca0020009c000003940000613d000005cb0020009c000001130000c13d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d16af12f00000040f000004680000013d000005bc0020009c000002d00000213d000005bf0020009c000004a00000613d000005c00020009c000001130000c13d0000000001000416000000000001004b000001130000c13d000000cc01000039000000000101041a000002300000013d000000400100043d0000006402100039000005b10300004100000000003204350000004402100039000005b2030000410000000000320435000000240210003900000027030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad010080410000004001100210000005b4011001c7000016b100010430000005df0020009c000004560000613d000005e00020009c000004210000613d000005e10020009c000001130000c13d0000000001000416000000000001004b000001130000c13d000000db01000039000004520000013d000005d60020009c000004640000613d000005d70020009c000004390000613d000005d80020009c000001130000c13d0000000001000416000000000001004b000001130000c13d000000d803000039000000000203041a000000010420019000000001012002700000007f0110618f0000001f0010008c00000000050000390000000105002039000000000054004b0000047d0000c13d000000d705000039000000000c05041a000000d605000039000000000b05041a000000d505000039000000000a05041a000000d405000039000000000905041a000000d305000039000000000805041a000000d205000039000000000705041a000000d105000039000000000605041a000000d005000039000000000505041a000000e00010043f000000000004004b000f00000006001d000e00000007001d000d00000008001d000c00000009001d000b0000000a001d000a0000000b001d00090000000c001d000800000005001d0000055d0000613d000000000030043f000000000001004b000006860000c13d0000010001000039000006910000013d000005cc0020009c0000046f0000613d000005cd0020009c0000044e0000613d000005ce0020009c000001130000c13d0000000001000416000000000001004b000001130000c13d000000db01000039000000000101041a000000da02000039000000000202041a000000d903000039000000000303041a000000e00030043f000001000020043f000001200010043f000005ec01000041000016b00001042e000005db0020009c000003420000613d000005dc0020009c000001130000c13d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d000000000010043f000000dc01000039000000200010043f0000004002000039000000000100001916af16720000040f000004520000013d000005d20020009c000003650000613d000005d30020009c000001130000c13d0000000001000416000000000001004b000001130000c13d0000000001000412001300000001001d001200000000003d000080050100003900000044030000390000000004000415000000130440008a00000005044002100000061c0200004116af16870000040f000005b001100197000000e00010043f000005e801000041000016b00001042e000005c80020009c000004060000613d000005c90020009c000001130000c13d000000e40030008c000001130000413d0000000402100370000000000202043b000f00000002001d000005b00020009c000001130000213d0000004402100370000000000202043b000e00000002001d0000002402100370000000000202043b000c00000002001d0000006402100370000000000202043b000d00000002001d000005ed0020009c000001130000213d0000008402100370000000000402043b000005ed0040009c000001130000213d0000002302400039000000000032004b000001130000813d0000000405400039000000000251034f000000000202043b000005ed0020009c0000076a0000213d0000001f0720003900000650077001970000003f0770003900000650077001970000061a0070009c0000076a0000213d000000e007700039000000400070043f000000e00020043f00000000042400190000002404400039000000000034004b000001130000213d0000002003500039000000000431034f00000650052001980000001f0620018f00000100035000390000026f0000613d0000010007000039000000000804034f000000008908043c0000000007970436000000000037004b0000026b0000c13d000000000006004b0000027c0000613d000000000454034f0000000305600210000000000603043300000000065601cf000000000656022f000000000404043b0000010005500089000000000454022f00000000045401cf000000000464019f000000000043043500000100022000390000000000020435000000a402100370000000000202043b000b00000002001d000005b00020009c000001130000213d000000c401100370000000000101043b000a00000001001d0000061b0100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b0000000002000411000000000012004b000007a30000c13d0000061c0100004100000000001004430000000001000412000000040010044300000024000004430000000001000414000005ad0010009c000005ad01008041000000c0011002100000061d011001c7000080050200003916af16aa0000040f0000000100200190000011b60000613d000000000201043b000000400300043d0000061e01000041000900000003001d00000000001304350000000001000414000005b002200197000800000002001d000000040020008c0000083e0000c13d0000000104000031000000200040008c0000002004008039000008690000013d000005c20020009c000004b60000613d000005c30020009c000001130000c13d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d000000000010043f000000cf01000039000000200010043f0000004002000039000000000100001916af16720000040f0000000202100039000000000202041a0000000103100039000000000303041a000000000101041a000005b001100197000000e00010043f000001000030043f000000ff0120018f000001200010043f000005ec01000041000016b00001042e000005bd0020009c000004d60000613d000005be0020009c000001130000c13d0000000001000416000000000001004b000001130000c13d0000012001000039000000400010043f0000000301000039000000e00010043f000005e201000041000001000010043f0000002001000039000001200010043f000000e001000039000001400200003916af11e50000040f000001200110008a000005ad0010009c000005ad010080410000006001100210000005e3011001c7000016b00001042e000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d0000006502000039000000000202041a000005b0022001970000000003000411000000000032004b000004f30000c13d000000000001004b000006290000c13d000005b301000041000000e00010043f0000002001000039000000e40010043f0000001901000039000001040010043f0000064c01000041000001240010043f0000064901000041000016b100010430000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000402100370000000000202043b000f00000002001d000005ed0020009c000001130000213d0000000f0230006a000e00000002001d000005ee0020009c000001130000213d0000000e02000029000001240020008c000001130000413d0000020002000039000000400020043f000000d003000039000000000303041a000005b003300197000000e00030043f000000d103000039000000000303041a000001000030043f000000d203000039000000000303041a000001200030043f000000d303000039000000000303041a000001400030043f000000d403000039000000000303041a000001600030043f000000d503000039000000000303041a000001800030043f000000d603000039000000000303041a000001a00030043f000000d703000039000000000303041a000001c00030043f000000d807000039000000000407041a000000010540019000000001034002700000007f0330618f0000001f0030008c00000000060000390000000106002039000000000664013f00000001006001900000047d0000c13d000002000030043f000000000005004b000006f40000613d000000000070043f000000000003004b000006fc0000c13d0000002003000039000007090000013d0000000001000416000000000001004b000001130000c13d000000d501000039000000000101041a000f00000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b0000000f0010006b0000000002000019000003630000813d000000d602000039000000000202041a000000000012004b0000000002000019000003630000a13d000000d201000039000000000101041a000000db02000039000000000202041a000000000012004b00000000020000390000000102004039000000010120018f000004680000013d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d0000014002000039000000400020043f000000e00000043f000001000000043f000001200000043f000000000010043f000000cf01000039000000200010043f0000004002000039000000000100001916af16720000040f000f00000001001d000001400100003916af11c80000040f0000000f03000029000000000103041a000005b001100197000001400010043f0000000102300039000000000202041a000001600020043f0000000202300039000000000202041a000000ff0220018f000001800020043f000000400200043d0000000001120436000001600300043d0000000000310435000001800100043d000000ff0110018f00000040032000390000000000130435000005ad0020009c000005ad02008041000000400120021000000632011001c7000016b00001042e000000a40030008c000001130000413d0000000402100370000000000202043b000f00000002001d0000002402100370000000000202043b000e00000002001d000005ed0020009c000001130000213d0000004402100370000000000402043b000005ed0040009c000001130000213d0000002302400039000000000032004b000001130000813d0000000405400039000000000251034f000000000202043b000005ed0020009c0000076a0000213d0000001f0720003900000650077001970000003f0770003900000650077001970000061a0070009c0000076a0000213d000000e007700039000000400070043f000000e00020043f00000000042400190000002404400039000000000034004b000001130000213d0000002003500039000000000431034f00000650052001980000001f0620018f0000010003500039000003c30000613d0000010007000039000000000804034f000000008908043c0000000007970436000000000037004b000003bf0000c13d000000000006004b000003d00000613d000000000454034f0000000305600210000000000603043300000000065601cf000000000656022f000000000404043b0000010005500089000000000454022f00000000045401cf000000000464019f0000000000430435000001000220003900000000000204350000006402100370000000000202043b000d00000002001d000005b00020009c000001130000213d0000008401100370000000000101043b000c00000001001d0000061b0100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b0000000002000411000000000012004b000007a30000c13d0000061c0100004100000000001004430000000001000412000000040010044300000024000004430000000001000414000005ad0010009c000005ad01008041000000c0011002100000061d011001c7000080050200003916af16aa0000040f0000000100200190000011b60000613d000000000201043b000000400300043d0000061e01000041000b00000003001d00000000001304350000000001000414000005b002200197000a00000002001d000000040020008c000007c10000c13d0000000103000031000000200030008c00000020040000390000000004034019000007ec0000013d0000000001000416000000000001004b000001130000c13d00000627010000410000000000100443000000000100041000000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c70000800a0200003916af16aa0000040f0000000100200190000011b60000613d000000c00500043d000000000301043b000000d001000039000000000201041a0000000001000414000005b004200197000000040040008c000005040000c13d00000001020000390000000109000031000005dd0000013d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000f00000001001d000005b00010009c000001130000213d0000064d01000041000000e00010043f0000000001000410000000e40010043f00000000010004140000000f06000029000000040060008c000005220000c13d0000000103000031000000200030008c0000002004000039000000000403401900000000050000190000054a0000013d0000000001000416000000000001004b000001130000c13d0000006501000039000000000201041a000005b0052001970000000003000411000000000035004b000004f30000c13d000005fe02200197000000000021041b0000000001000414000005ad0010009c000005ad01008041000000c0011002100000060a011001c70000800d02000039000000030300003900000614040000410000000006000019000006370000013d0000000001000416000000000001004b000001130000c13d000000cd01000039000000000101041a000000e00010043f000005e801000041000016b00001042e000000440030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000402100370000000000302043b000005b00030009c000001130000213d0000002401100370000000000201043b000000000103001916af120c0000040f000004680000013d0000000001000416000000000001004b000001130000c13d16af12cb0000040f000000400200043d0000000000120435000005ad0020009c000005ad020080410000004001200210000005eb011001c7000016b00001042e0000000001000416000000000001004b000001130000c13d000000cb03000039000000000203041a000000010420019000000001012002700000007f0110618f0000001f0010008c00000000050000390000000105002039000000000552013f0000000100500190000004fc0000613d0000060c01000041000000000010043f0000002201000039000000040010043f0000060d01000041000016b1000104300000000001000416000000000001004b000001130000c13d000000d601000039000000000101041a000f00000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b0000000f0010006b0000055a0000a13d000000d201000039000000000101041a000000db02000039000000000202041a000000000012004b00000000010000390000000101008039000000010110018f000004680000013d000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d0000009702000039000000000202041a000005e903000041000000e00030043f000000e40010043f0000000001000414000005b002200197000000040020008c000005630000c13d0000000104000031000000200040008c0000002004008039000005880000013d000000840030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000004402100370000000000302043b000005b00030009c000001130000213d0000006401100370000000000201043b000000000103001916af120c0000040f00000002030003670000000402300370000000000402043b00000000021400a9000000000004004b000004cc0000613d00000000044200d9000000000014004b000004d00000c13d0000002401300370000000000301043b0000004d0030008c000005b60000a13d0000060c01000041000000000010043f0000001101000039000000040010043f0000060d01000041000016b100010430000000240030008c000001130000413d0000000002000416000000000002004b000001130000c13d0000000401100370000000000101043b000005b00010009c000001130000213d0000006502000039000000000202041a000005b0022001970000000003000411000000000032004b000004f30000c13d000000000001004b000006be0000c13d000005b301000041000000e00010043f0000002001000039000000e40010043f0000002601000039000001040010043f000005e501000041000001240010043f000005e601000041000001440010043f000005e701000041000016b100010430000005b301000041000000e00010043f0000002001000039000000e40010043f000001040010043f0000063b01000041000001240010043f0000064901000041000016b100010430000000e00010043f000000000004004b000005540000613d000000000030043f000000000001004b0000064b0000c13d0000010001000039000006560000013d000005ad0050009c000005ad020000410000000002054019000005e4022000d1000005ad0010009c000005ad01008041000000c001100210000000000121019f000000000003004b000e00000005001d000f00000003001d000005ba0000c13d000000000204001916af16a50000040f0000006003100270000005ad093001970000000e03000029000000000039004b000000000403001900000000040940190000001f0540018f000005af064001980000000004630019000005cd0000613d000000000701034f000000007807043c0000000003830436000000000043004b0000051d0000c13d000005cd0000013d000005ad0010009c000005ad01008041000000c001100210000005ea011001c7000000000206001916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f0000002007400190000000e005700039000005370000613d000000e008000039000000000901034f000000009a09043c0000000008a80436000000000058004b000005330000c13d000000000006004b000005440000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000c00500043d00000001002001900000063b0000613d0000000f060000290000001f01400039000000600110018f000000e002100039000000400020043f000000200030008c000005930000813d000005ad0050009c000005ad05008041000005e4015000d1000016b1000104300000065102200197000001000020043f000000000001004b00000120010000390000010001006039000006560000013d0000000101000039000000010110018f000004680000013d0000065102200197000001000020043f000000000001004b00000120010000390000010001006039000006910000013d000005ad0010009c000005ad01008041000000c001100210000005ea011001c716af16aa0000040f000000e00a0000390000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f0000002007400190000000e005700039000005770000613d000000000801034f000000008908043c000000000a9a043600000000005a004b000005730000c13d000000000006004b000005840000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f00030000000103550000000100200190000006c00000613d0000001f01400039000000600110018f000000e001100039000000400010043f000000200040008c000001130000413d000000e00200043d00000000002104350000004001100210000005eb011001c7000016b00001042e000000d003000039000000000303041a00000100041001bf000000e00700043d000e00000007001d0000064e050000410000000000540435000005b00330019700000104041001bf000000000034043500000124031000390000000000730435000000440300003900000000003204350000016001100039000000400010043f000000000106001916af140c0000040f000000400100043d0000000e020000290000000000210435000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f000005b5011001c70000800d0200003900000002030000390000064f040000410000000f05000029000006370000013d000000000003004b000006e30000c13d000000010120011a000004680000013d0000060a011001c70000800902000039000000000500001916af16a50000040f0000006003100270000005ad093001970000000e03000029000000000039004b000000000403001900000000040940190000001f0540018f000005af064001980000000004630019000005cd0000613d000000000701034f000000007807043c0000000003830436000000000043004b000005c90000c13d000000000005004b000005da0000613d000000000661034f0000000305500210000000000704043300000000075701cf000000000757022f000000000606043b0000010005500089000000000656022f00000000055601cf000000000575019f0000000000540435000100000009001f00030000000103550000000f03000029000000000009004b000005f00000c13d000000400100043d0000000100200190000006190000613d0000000000310435000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f000005b5011001c70000800d0200003900000001030000390000062904000041000006370000013d000005ed0090009c0000076a0000213d0000001f0490003900000650044001970000003f044000390000065005400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000005ed0050009c0000076a0000213d00000001006001900000076a0000c13d000000400050043f000000000694043600000650049001980000001f0990018f000000000146001900000003050003670000060b0000613d000000000705034f000000007807043c0000000006860436000000000016004b000006070000c13d000000000009004b000005df0000613d000000000445034f0000000306900210000000000501043300000000056501cf000000000565022f000000000404043b0000010006600089000000000464022f00000000046401cf000000000454019f0000000000410435000005df0000013d000000440210003900000628030000410000000000320435000000240210003900000010030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b100010430000000ca02000039000000000302041a000005fe03300197000000000313019f000000000032041b000000e00010043f0000000001000414000005ad0010009c000005ad01008041000000c0011002100000064a011001c70000800d0200003900000001030000390000064b0400004116af16a50000040f0000000100200190000001130000613d000007710000013d0000000002350019000000000032004b000001130000213d000000000451034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b000006460000c13d000006d00000013d000006080200004100000000040000190000000003040019000000000402041a0000010005300039000000000045043500000001022000390000002004300039000000000014004b0000064d0000413d0000012001300039000000e00210008a000000e00100003916af11d30000040f0000002001000039000000400200043d000f00000002001d0000000002120436000000e00100003916af11e50000040f0000000f020000290000000001210049000005ad0010009c000005ad010080410000006001100210000005ad0020009c000005ad020080410000004002200210000000000121019f000016b00001042e000000400a00043d000006460100004100000000001a04350000000401a000390000000f020000290000000000210435000000c00300043d00000000010004140000000e04000029000000040040008c000007680000613d000005ad00a0009c000005ad0200004100000000020a40190000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f000000000003004b000f0000000a001d000007420000c13d0000060d011001c7000000000204001916af16a50000040f0000006003100270000005ad033001970000000f0a000029000007640000013d000005f50200004100000000040000190000000003040019000000000402041a0000010005300039000000000045043500000001022000390000002004300039000000000014004b000006880000413d0000012001300039000000e00210008a000000e00100003916af11d30000040f000000400300043d000700000003001d000001000130003900000120020000390000000000210435000000e00130003900000009020000290000000000210435000000c0013000390000000a020000290000000000210435000000a0013000390000000b02000029000000000021043500000080013000390000000c02000029000000000021043500000060013000390000000d02000029000000000021043500000040013000390000000e02000029000000000021043500000020013000390000000f0200002900000000002104350000000801000029000005b00110019700000000001304350000012002300039000000e00100003916af11e50000040f00000007020000290000000001210049000005ad0010009c000005ad01008041000005ad0020009c000005ad0200804100000060011002100000004002200210000000000121019f000016b00001042e16af11f70000040f000007710000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b000006cc0000c13d000000000005004b000006dd0000613d000000000464034f0000000305500210000000000602043300000000065601cf000000000656022f000000000404043b0000010005500089000000000454022f00000000045401cf000000000464019f00000000004204350000006002300210000005ad0010009c000005ad010080410000004001100210000000000121019f000016b1000104300000000a040000390000000101000039000000010030019000000000054400a9000000010400603900000000011400a900000001033002720000000004050019000006e50000c13d000000000001004b000006fa0000c13d0000060c01000041000000000010043f0000001201000039000000040010043f0000060d01000041000016b1000104300000065104400197000002200040043f000000000003004b00000020040000390000000004006039000007050000013d00000000011200d9000004680000013d000005f5050000410000000004000019000000000605041a0000022007400039000000000067043500000001055000390000002004400039000000000034004b000006fe0000413d0000003f034000390000065003300197000006330030009c0000076a0000213d0000020003300039000000400030043f000001e00020043f000000dd02000039000000000202041a000005b0002001980000071d0000c13d0000000f02000029000000a403200039000000000231034f000000000202043b000006370020009c000007870000413d000000400100043d00000044021000390000064503000041000000000032043500000024021000390000001f030000390000061e0000013d000001a00100043d000d00000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000201043b0000000d0020006c00000c330000813d0000000201000367000001800300043d000000000032004b000007100000a13d0000000f020000290000004402200039000000000221034f000000000202043b000001200300043d000000000023004b000007100000613d000000400100043d00000064021000390000063503000041000000000032043500000044021000390000063603000041000000000032043500000024021000390000002403000039000001ae0000013d00000647011001c70000800902000039000d00000003001d000000000500001916af16a50000040f0000006003100270000005ad033001970000000d0030006c0000000d0400002900000000040340190000001f0540018f000005af064001980000000f0a00002900000000046a0019000007570000613d000000000701034f00000000080a0019000000007907043c0000000008980436000000000048004b000007530000c13d000000000005004b000007640000613d000000000661034f0000000305500210000000000704043300000000075701cf000000000757022f000000000606043b0000010005500089000000000656022f00000000055601cf000000000575019f0000000000540435000100000003001f00030000000103550000000100200190000007760000613d0000064800a0009c000007700000413d0000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b1000104300000004000a0043f000000c00100043d000005ad0010009c000005ad01008041000005e4011000d1000016b00001042e000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b000007820000c13d000006d00000013d0000002004300039000000000341034f000000000303043b000006370030009c000007930000413d000000400100043d00000044021000390000064403000041000000000032043500000024021000390000001d030000390000061e0000013d0000002005400039000000000451034f000000000404043b000006380040009c000007aa0000413d000000400100043d000000440210003900000643030000410000000000320435000005b30200004100000000002104350000002402100039000000200300003900000000003204350000000402100039000006230000013d000000400100043d00000044021000390000062a030000410000000000320435000000240210003900000014030000390000061e0000013d000000e00650008a000000000561034f000000000505043b000005b00050009c000001130000213d000000000005004b000007b80000c13d000000400100043d000000440210003900000642030000410000000000320435000000240210003900000017030000390000061e0000013d0000004007600039000000000671034f000000000606043b000000000006004b000008350000c13d000000400100043d000000440210003900000641030000410000061b0000013d0000000b02000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c70000000a0200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000b05700029000007db0000613d000000000801034f0000000b09000029000000008a08043c0000000009a90436000000000059004b000007d70000c13d000000000006004b000007e80000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f00030000000103550000000100200190000008d60000613d0000001f01400039000000600210018f0000000b01200029000000000021004b00000000020000390000000102004039000005ed0010009c0000076a0000213d00000001002001900000076a0000c13d000000400010043f000000200030008c000001130000413d0000000b010000290000000001010433000005b00010009c000001130000213d000000e00500003900000000020004110000000f030000290000000e0400002916af15810000040f000000d501000039000000000101041a000e00000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000201043b0000000e0120006c000009960000a13d000000d603000039000000000303041a000000000032004b000009a70000813d000000d202000039000000000202041a000000db03000039000000000303041a000000000023004b000009b20000813d000000d702000039000000000202041a000000000002004b00000000030000190000082b0000613d000005b002200198000006ee0000613d000000dd03000039000000000303041a0000000004000411000000000343013f000005b003300197000005b00220012900000000032300d9000000000031004b000009b90000a13d000000ce01000039000000000101041a000000ff00100190000009c00000c13d000000400100043d00000044021000390000063103000041000009990000013d0000002008700039000000000781034f000000000707043b000000000007004b000008e70000c13d000000400100043d000000440210003900000640030000410000061b0000013d0000000902000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c7000000080200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000905700029000008580000613d000000000801034f0000000909000029000000008a08043c0000000009a90436000000000059004b000008540000c13d000000000006004b000008650000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f00030000000103550000000100200190000008f00000613d0000001f01400039000000600210018f0000000901200029000000000021004b00000000020000390000000102004039000005ed0010009c0000076a0000213d00000001002001900000076a0000c13d000000400010043f000000200040008c000001130000413d00000009010000290000000001010433000005b00010009c000001130000213d000000e00500003900000000020004110000000e030000290000000d0400002916af15810000040f000000d501000039000000000101041a000d00000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000201043b0000000d0120006c000009960000a13d000000d603000039000000000303041a000000000032004b000009a70000813d000000d202000039000000000202041a000000db03000039000000000303041a000000000023004b000009b20000813d000000d702000039000000000202041a000000000002004b0000000003000019000008a80000613d000005b002200198000006ee0000613d000000dd03000039000000000303041a0000000004000411000000000343013f000005b003300197000005b00220012900000000032300d9000000000031004b000009b90000a13d000000c00100043d0000000f020000290000000000210435000000cf01000039000000200010043f000000c00100043d000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000121019f0000060f011001c7000080100200003916af16aa0000040f0000000100200190000001130000613d000000400200043d0000060e0020009c0000076a0000213d000000000101043b0000006003200039000000400030043f000000000301041a000005b00330019800000000033204360000000104100039000000000404041a000000000043043500000040022000390000000201100039000000000101041a000000ff0110018f000000000012043500000aa40000c13d000000400100043d000000440210003900000626030000410000000000320435000000240210003900000015030000390000061e0000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b000008e20000c13d000006d00000013d000000000067004b000009010000a13d000000400100043d00000044021000390000063f030000410000000000320435000000240210003900000019030000390000061e0000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b000008fc0000c13d000006d00000013d0000002008800039000000000981034f000000000909043b000000000079004b0000090a0000a13d000000400100043d00000044021000390000063e030000410000078f0000013d000000000a24001900000000003a004b0000099d0000813d000000650a000039000000000a0a041a000005b00aa00197000000000b0004110000000000ba004b000009ae0000c13d000000d00b000039000000000a0b041a000005fe0aa0019700000000055a019f00000000005b041b000000600580008a000000000551034f000000000505043b000000d10a00003900000000005a041b000000d205000039000000000065041b000000d305000039000000000075041b000000d405000039000000000095041b000000d505000039000000000025041b000000d602000039000000000032041b000000d702000039000000000042041b0000008002800039000000000221034f0000000e03000029000000230330008a000000000202043b00000605043001970000060505200197000000000645013f000000000045004b00000000040000190000060504002041000000000032004b00000000030000190000060503004041000006050060009c000000000403c019000000000004004b0000013c0000613d0000000f032000290000000402300039000000000221034f000000000202043b000005ed0020009c0000013c0000213d0000002403300039000000000420007900000605054001970000060506300197000000000756013f000000000056004b00000000050000190000060505004041000000000043004b00000000040000190000060504002041000006050070009c000000000504c019000000000005004b0000013c0000c13d000000d804000039000000000404041a000000010040019000000001054002700000007f0550618f0000001f0050008c00000000060000390000000106002039000000000464013f00000001004001900000047d0000c13d000000c00400043d000000200050008c0000096f0000413d000000d80600003900000000006404350000001f042000390000000504400270000006060640009a000000200020008c000005f506004041000000c00400043d0000001f055000390000000505500270000006060550009a000000000056004b0000096f0000813d000000000046041b0000000106600039000000000056004b0000096b0000413d0000001f0020008c00000a050000a13d000000d80500003900000000005404350000065006200197000005f505000041000000c00700043d000000000067004b000009880000813d00000652057001670000002007700039000000000076004b00000000070620190000000007750019000005f50500004100000005077002700000063c0770009a0000000008340019000000000881034f000000000808043b000000000085041b00000020044000390000000105500039000000000075004b000009800000c13d000000000026004b000009930000813d0000000306200210000000f80660018f000006520660027f00000652066001670000000003340019000000000131034f000000000101043b000000000161016f000000000015041b000000010120021000000001011001bf00000a100000013d000000400100043d00000044021000390000062b030000410000000000320435000000240210003900000018030000390000061e0000013d000000400100043d00000064021000390000063903000041000000000032043500000044021000390000063a03000041000000000032043500000024021000390000002b03000039000001ae0000013d000000400100043d00000044021000390000062c03000041000000000032043500000024021000390000000e030000390000061e0000013d000000400100043d00000044021000390000063b030000410000079b0000013d000000400100043d00000044021000390000062d030000410000000000320435000000240210003900000016030000390000061e0000013d000000400100043d00000044021000390000062e030000410000000000320435000000240210003900000011030000390000061e0000013d0000000101000039000000000201041a000000020020008c000009c80000c13d000000400100043d00000044021000390000063003000041000007190000013d0000000202000039000000000021041b000000cd01000039000000000201041a000000cc01000039000000000101041a000005b001100197000b00000001001d000e00000002001d16af120c0000040f000000000300041600000000021300a9000000000003004b000009d90000613d00000000033200d9000000000013004b000004d00000c13d0000061f0120012a000900000001001d0000000c0010006c00000a790000813d000000400100043d00000044021000390000062f03000041000008ec0000013d001100000001001d0000000001000415000000110110008a0004000500100218000005ef010000410000000000100443000000000100041000000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000000001004b00000a970000c13d0000000601000029000000ff0110018f000000010010008c00000004010000290000000501100270000000000100003f000000010100603f00000a9a0000c13d000000c00100043d000000a00200043d000000000002004b000000a80000c13d000001000200008a000000060220017f00000001022001bf000000ab0000013d000000000002004b00000a0a0000613d0000000003340019000000000131034f000000000401043b0000000301200210000006520110027f0000065201100167000000000114016f0000000102200210000000000121019f000000d802000039000000000012041b000006120100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000000001004b000004d00000613d00000613020000410000000000200443000000010110008a00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000005b001100197000000dd03000039000000000203041a000005fe02200197000000000112019f000000000013041b0000002002000039000000400100043d0000000002210436000000d003000039000000000303041a000005b0033001970000000000320435000000d102000039000000000202041a00000040031000390000000000230435000000d202000039000000000202041a00000060031000390000000000230435000000d302000039000000000202041a00000080031000390000000000230435000000d402000039000000000202041a000000a0031000390000000000230435000000d502000039000000000202041a000000c0031000390000000000230435000000d602000039000000000202041a000000e0031000390000000000230435000000d702000039000000000202041a00000120031000390000012004000039000000000043043500000100031000390000000000230435000000d802000039000000000402041a000000010540019000000001024002700000007f0220618f0000001f0020008c00000000030000390000000103002039000000000334013f00000001003001900000047d0000c13d000001400310003900000000002304350000016003100039000000000005004b00000bfd0000613d000000d804000039000000000040043f000000000002004b000000000400001900000c020000613d000005f50500004100000000040000190000000006340019000000000705041a000000000076043500000001055000390000002004400039000000000024004b00000a710000413d00000c020000013d0000000c010000290008061f001000d5000000000001004b00000a810000613d00000008020000290000000c012000fa0000061f0010009c000004d00000c13d0000000b010000290000000e0200002916af120c0000040f000000000001004b000006ee0000613d00000008021000f90000000d01000029000c00000002001d16af13050000040f0000062001000041000000400200043d000e00000002001d000000000012043500000000010004140000000a02000029000000040020008c00000b660000c13d0000000103000031000000200030008c0000002004000039000000000403401900000b910000013d00000004010000290000000501100270000000000100003f000000400100043d0000006402100039000005f10300004100000000003204350000004402100039000005f203000041000000000032043500000024021000390000002e03000039000001ae0000013d0000000101000039000d00000001001d000000000101041a000000020010008c000009c40000613d00000002010000390000000102000039000000000012041b000000cd01000039000000000201041a000000cc01000039000000000101041a000005b001100197000700000001001d000900000002001d16af120c0000040f000000000300041600000000021300a9000000000003004b00000abb0000613d00000000033200d9000000000013004b000004d00000c13d0000061f0120012a0000000a0010006c000009dd0000413d0000000a010000290006061f001000d5000000000001004b00000ac60000613d00000006020000290000000a012000fa0000061f0010009c000004d00000c13d0000000701000029000000090200002916af120c0000040f000000000001004b000006ee0000613d00000006021000f90000000b0100002916af13050000040f000000c00100043d0000000f020000290000000000210435000000cf01000039000000200010043f000000c00100043d000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000121019f0000060f011001c7000080100200003916af16aa0000040f0000000100200190000001130000613d000000400300043d0000060e0030009c0000076a0000213d000000000401043b0000006001300039000000400010043f000000000104041a000005b00110019700000000051304360000000102400039000000000202041a000000000025043500000040033000390000000204400039000000000404041a000000ff0440018f000b00000004001d000000000043043516af120c0000040f0009000c001000bd0000000c0000006b00000afb0000613d00000009030000290000000c023000fa000000000012004b000004d00000c13d0000000b010000290000004d0010008c000004d00000213d0000000b0000006b00000b0c0000613d0000000a01000039000d00010000003d0000000b04000029000000010040019000000000021100a90000000101006039000d000d001000bd000b00010040027a000000000102001900000b020000c13d0000000d0000006b000006ee0000613d000000400200043d0000062001000041000b00000002001d000000000012043500000000010004140000000802000029000000040020008c00000cc50000c13d0000000104000031000000200040008c000000200400803900000cf00000013d00000651066001970000000000650435000000000004004b000000200600003900000000060060390000003f0460003900000650054001970000000004350019000000000054004b00000000050000390000000105004039000005ed0040009c0000076a0000213d00000001005001900000076a0000c13d000000400040043f00000100011000390000000000310435000000dd01000039000000000101041a000005b00010019800000c140000c13d00000002020003670000002401200370000000000101043b000000a401100039000000000312034f000000000303043b000005f80030009c000007160000213d0000002001100039000000000412034f000000000404043b000005f80040009c0000078c0000213d0000002001100039000000000512034f000000000505043b000005f90050009c000007980000213d000000e00610008a000000000162034f000000000701043b000005b00070009c000001130000213d000000400100043d000000000007004b000007b20000613d0000004007600039000000000672034f000000000606043b000000000006004b000007be0000613d0000002008700039000000000782034f000000000707043b000000000007004b0000083b0000613d000000000067004b000008ea0000213d0000002006800039000000000262034f000000000202043b000000000072004b000009070000213d0000000002350019000000000042004b0000099e0000813d000000c00200043d000000000302041a0000ff000030019000000d970000c13d00000064021000390000061803000041000000000032043500000044021000390000061903000041000009a30000013d0000000e02000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c70000000a0200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000e0570002900000b800000613d000000000801034f0000000e09000029000000008a08043c0000000009a90436000000000059004b00000b7c0000c13d000000000006004b00000b8d0000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000c3a0000613d0000001f01400039000000600110018f0000000e04100029000000000014004b00000000020000390000000102004039000d00000004001d000005ed0040009c0000076a0000213d00000001002001900000076a0000c13d0000000d02000029000000400020043f000000200030008c000001130000413d0000000e020000290000000002020433000005b00020009c000001130000213d00000621040000410000000d0500002900000000004504350000000404500039000000000500041100000000005404350000000004000414000000040020008c00000bd90000613d0000000d01000029000005ad0010009c000005ad010080410000004001100210000005ad0040009c000005ad04008041000000c003400210000000000113019f0000060d011001c716af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000d0570002900000bc60000613d000000000801034f0000000d09000029000000008a08043c0000000009a90436000000000059004b00000bc20000c13d000000000006004b00000bd30000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000c4b0000613d0000001f01400039000000600110018f0000000d01100029000005ed0010009c0000076a0000213d000000400010043f000000200030008c000001130000413d0000000d010000290000000001010433000e00000001001d00000009010000290000000f0200002916af138b0000040f0000000e0000006b0000000e02000029000000640200603900000000010004160000000003020019000e0000001200ad000000000001004b00000bf00000613d0000000e011000f9000000000031004b000004d00000c13d000000400200043d0000062201000041000f00000002001d000000000012043500000000010004140000000a02000029000000040020008c00000c5c0000c13d0000000103000031000000200030008c0000002004000039000000000403401900000c870000013d00000651044001970000000000430435000000000002004b00000020040000390000000004006039000005ad0010009c000005ad0100804100000040011002100000016002400039000005ad0020009c000005ad020080410000006002200210000000000112019f0000000002000414000005ad0020009c000005ad02008041000000c002200210000000000121019f0000060a011001c70000800d0200003900000001030000390000063d04000041000006370000013d0000000001020433000300000001001d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000030010006c00000c330000813d000000020200036700000006030000290000000003030433000000000031004b00000b2f0000a13d0000002401200370000000000101043b0000004401100039000000000112034f000000000101043b00000004030000290000000003030433000000000013004b000007380000c13d00000b2f0000013d000000400100043d00000044021000390000063403000041000000000032043500000024021000390000001a030000390000061e0000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000c460000c13d000006d00000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000c570000c13d000006d00000013d0000000f02000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c70000000a0200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000f0570002900000c760000613d000000000801034f0000000f09000029000000008a08043c0000000009a90436000000000059004b00000c720000c13d000000000006004b00000c830000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000d110000613d0000001f01400039000000600210018f0000000f01200029000000000021004b00000000020000390000000102004039000005ed0010009c0000076a0000213d00000001002001900000076a0000c13d000000400010043f000000200030008c000001130000413d0000000f010000290000000001010433000005b00010009c000001130000213d0000000e02000029000027100220011a000f00000002001d16af151b0000040f00000000010004160000000f0110006c000004d00000413d0000000c0210006c000004d00000413d000000d001000039000000000101041a000005b00110019716af151b0000040f000000400100043d0000006002100039000000c00600043d0000000c03000029000000000032043500000040021000390000000f03000029000000000032043500000020021000390000000003000416000000000032043500000009020000290000000000210435000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f00000610011001c70000800d0200003900000003030000390000062504000041000000000500041116af16a50000040f0000000100200190000001130000613d0000000101000039000000000011041b000007710000013d0000000b02000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c7000000080200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000b0570002900000cdf0000613d000000000801034f0000000b09000029000000008a08043c0000000009a90436000000000059004b00000cdb0000c13d000000000006004b00000cec0000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000d220000613d0000001f01400039000000600110018f0000000b03100029000000000013004b00000000020000390000000102004039000700000003001d000005ed0030009c0000076a0000213d00000001002001900000076a0000c13d0000000702000029000000400020043f000000200040008c000001130000413d0000000b020000290000000002020433000005b00020009c000001130000213d0000062103000041000000070400002900000000003404350000000403400039000000000400041100000000004304350000000003000414000000040020008c00000d330000c13d0000000701100029000005ed0010009c0000076a0000213d000000400010043f00000d650000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000d1d0000c13d000006d00000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000d2e0000c13d000006d00000013d0000000701000029000005ad0010009c000005ad010080410000004001100210000005ad0030009c000005ad03008041000000c003300210000000000113019f0000060d011001c716af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f0000002007400190000000070570002900000d4c0000613d000000000801034f0000000709000029000000008a08043c0000000009a90436000000000059004b00000d480000c13d000000000006004b00000d590000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000d860000613d0000001f01400039000000600110018f0000000701100029000005ed0010009c0000076a0000213d000000400010043f000000200030008c000001130000413d00000009020000290000000d012000fa00000007020000290000000002020433000000000002004b0000006402006039000d00000002001d0000000a0010002a000004d00000413d0000000a01100029000900000001001d0000000e0200002916af138b0000040f0000000d02000029000b000c002000bd0000000c0000006b00000d7a0000613d0000000b020000290000000c012000fa0000000d0010006c000004d00000c13d000000400200043d0000062201000041000e00000002001d000000000012043500000000010004140000000802000029000000040020008c00000da30000c13d0000000104000031000000200040008c000000200400803900000dce0000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000d920000c13d000006d00000013d000005fa0010009c00000e2c0000a13d0000060c0100004100000000001204350000004101000039000000040010043f000000c00100043d000005ad0010009c000005ad0100804100000040011002100000060d011001c7000016b1000104300000000e02000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f00000601011001c7000000080200002916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0640018f00000020074001900000000e0570002900000dbd0000613d000000000801034f0000000e09000029000000008a08043c0000000009a90436000000000059004b00000db90000c13d000000000006004b00000dca0000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f0003000000010355000000010020019000000e460000613d0000001f01400039000000600110018f0000000e02100029000000000012004b00000000010000390000000101004039000005ed0020009c0000076a0000213d00000001001001900000076a0000c13d000000400020043f000000200040008c000001130000413d0000000e010000290000000001010433000005b00010009c000001130000213d0000000b03000029000027100530011a0000002003200039000006230400004100000000004304350000006403200039000e00000005001d000000000053043500000044032000390000000000130435000000640100003900000000001204350000000001000411000005b0031001970000002401200039000d00000003001d0000000000310435000006240020009c0000076a0000213d000000a001200039000000400010043f0000000f0100002916af140c0000040f0000000e020000290000000c0120006b000004d00000413d000000400200043d000000200320003900000623040000410000000000430435000000640320003900000000001304350000000001000410000005b0011001970000004403200039000000000013043500000024012000390000000d03000029000000000031043500000064010000390000000000120435000006240020009c0000076a0000213d000000a001200039000000400010043f0000000f0100002916af140c0000040f000000400100043d00000060021000390000000a03000029000000000032043500000040021000390000000e03000029000000000032043500000020021000390000000c03000029000000000032043500000009020000290000000000210435000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f00000610011001c70000800d020000390000000303000039000006250400004100000000050004110000000f0600002916af16a50000040f0000000100200190000001130000613d00000cc20000013d0000002402100039000005fb0300004100000000003204350000004402100039000000c00300043d000000000400041400000060050000390000000000520435000005fc0200004100000000002104350000006402100039000000000002043500000004021000390000000000020435000005ad0010009c000005ad010080410000004001100210000005ad0040009c000005ad04008041000000c002400210000000000112019f000005fd011001c7000000000003004b00000e570000c13d000080060200003900000e5a0000013d000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000e520000c13d000006d00000013d00008009020000390000800604000039000000010500003916af16a50000040f000000010020019000000e620000613d000000000101043b000000000001004b00000e8b0000c13d000000010100003100000e660000013d00030000000103550000006001100270000105ad0010019d000005ad01100197000000c00200043d0000000003120019000000000013004b000001130000213d00000650041001980000001f0510018f0000000306200367000000400200043d000000000342001900000e760000613d000000000706034f0000000008020019000000007907043c0000000008980436000000000038004b00000e720000c13d000000000005004b00000e830000613d000000000446034f0000000305500210000000000603043300000000065601cf000000000656022f000000000404043b0000010005500089000000000454022f00000000045401cf000000000464019f0000000000430435000005ad0010009c000005ad010080410000006001100210000005ad0020009c000005ad020080410000004002200210000000000112019f000016b100010430000005b0031001970000009701000039000000000401041a000005fe02400197000000000232019f000000000021041b000000c00100043d000005ef020000410000000000200443000200000004001d000000000141016f000300000003001d000000000131019f00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000000001004b0000013c0000613d000000400200043d000005ff01000041000600000002001d0000000001120436000400000001001d000000c00100043d0000000003010019000000020110017f00000003041001af0000000001000414000000040040008c00000ee60000613d0000000602000029000005ad0020009c000005ad020080410000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f000000000003004b00000ec10000c13d00000601011001c7000000000204001916af16a50000040f0000006003100270000005ad0330019700000ee20000013d00000600011001c70000800902000039000100000003001d000000000500001916af16a50000040f0000006003100270000005ad03300197000000010030006c000000010400002900000000040340190000001f0540018f000005af06400198000000060460002900000ed50000613d000000000701034f0000000608000029000000007907043c0000000008980436000000000048004b00000ed10000c13d000000000005004b00000ee20000613d000000000661034f0000000305500210000000000704043300000000075701cf000000000757022f000000000606043b0000010005500089000000000656022f00000000055601cf000000000575019f0000000000540435000100000003001f0003000000010355000000010020019000000f050000613d0000000601000029000005ed0010009c0000076a0000213d0000000601000029000000400010043f000000c00300043d000000000003004b00000f160000c13d000000000100041a0000ff000010019000000f1a0000c13d000000060300002900000064013000390000061802000041000000000021043500000044013000390000061902000041000000000021043500000024013000390000002b020000390000000000210435000005b3010000410000000000130435000000040130003900000020020000390000000000210435000005ad0030009c000005ad030080410000004001300210000005b4011001c7000016b100010430000000c00200043d0000000004320019000000000034004b000001130000213d000000000421034f0000001f0530018f000005af06300198000000400100043d0000000002610019000006d00000613d000000000704034f0000000008010019000000007907043c0000000008980436000000000028004b00000f110000c13d000006d00000013d000005ad0030009c000005ad03008041000005e4013000d1000016b1000104300000000101000039000000000011041b0000000b020000290000000d0020006b00000f2c0000c13d00000007020000290000000d0020006b00000f330000c13d0000000e01000029000005b00110019800000f440000c13d00000006030000290000004401300039000006170200004100000000002104350000002401300039000000120200003900000f390000013d000000060300002900000044013000390000060202000041000000000021043500000024013000390000001b0200003900000f390000013d000000060300002900000044013000390000060402000041000000000021043500000024013000390000001d020000390000000000210435000005b3010000410000000000130435000000040130003900000020020000390000000000210435000005ad0030009c000005ad03008041000000400130021000000603011001c7000016b10001043000000002020003670000002404200370000000000404043b0000000405400039000000000652034f000000000606043b000005b00060009c000001130000213d000000d008000039000000000708041a000005fe07700197000000000667019f000000000068041b0000002006500039000000000662034f000000000606043b000000d107000039000000000067041b0000004006500039000000000662034f000000000606043b000000d207000039000000000067041b0000006006500039000000000662034f000000000606043b000000d307000039000000000067041b0000008006500039000000000662034f000000000606043b000000d407000039000000000067041b000000a006500039000000000662034f000000000606043b000000d507000039000000000067041b000000c006500039000000000662034f000000000606043b000000d607000039000000000067041b000000e006500039000000000662034f000000000606043b000000d707000039000000000067041b0000010006500039000000000662034f00000000080000310000000007480049000000230970008a000000000706043b0000060506700197000006050a900197000000000ba6013f0000000000a6004b00000000060000190000060506004041000000000097004b000000000900001900000605090080410000060500b0009c000000000609c019000000000006004b000001130000c13d00000000044700190000000406400039000000000662034f000000000606043b000005ed0060009c000001130000213d000000000868004900000024044000390000060509800197000006050a400197000000000b9a013f00000000009a004b00000000090000190000060509004041000000000084004b000000000800001900000605080020410000060500b0009c000000000908c019000000000009004b000001130000c13d000000d808000039000000000808041a000000010080019000000001098002700000007f0990618f0000001f0090008c000000000a000039000000010a0020390000000008a8013f00000001008001900000047d0000c13d0000000005570019000000000552034f000000000805043b000000200090008c00000fbc0000413d000000d805000039000000000050043f0000001f058000390000000505500270000006060550009a000000200080008c000005f5050040410000001f079000390000000507700270000006060770009a000000000075004b00000fbc0000813d000000000005041b0000000105500039000000000075004b00000fb80000413d0000001f0080008c000000010780021000000fc60000a13d000000d808000039000000000080043f000006500960019800000fd10000c13d000005f508000041000000000a00001900000fdb0000013d000000000006004b000000000600001900000fcb0000613d000000000442034f000000000604043b0000000304800210000006520440027f0000065204400167000000000446016f000000000474019f00000fe70000013d000005f508000041000000000a000019000000000b4a0019000000000bb2034f000000000b0b043b0000000000b8041b0000000108800039000000200aa0003900000000009a004b00000fd30000413d000000000069004b00000fe60000813d0000000306600210000000f80660018f000006520660027f000006520660016700000000044a0019000000000442034f000000000404043b000000000464016f000000000048041b00000001047001bf000000d806000039000000000046041b000000cb04000039000000000604041a000000010060019000000001086002700000007f0880618f0000001f0080008c00000000070000390000000107002039000000000676013f00000001006001900000047d0000c13d0000004406200370000000000606043b0000000407600039000000000772034f000000000707043b0000001f0080008c0000100a0000a13d000000000040043f0000001f097000390000000509900270000006070990009a000000200070008c00000608090040410000001f088000390000000508800270000006070880009a000000000089004b0000100a0000813d000000000009041b0000000109900039000000000089004b000010060000413d0000001f0070008c00000001087002100000000309700210000000240a600039000010250000a13d000000000040043f000006500b7001970000000000b3004b0000102f0000813d000006520c300167000000200330003900000000003b004b00000000030b2019000000000c3c00190000060803000041000000050cc00270000006090dc0009a000000000c000019000000000eca0019000000000ee2034f000000000e0e043b0000000000e3041b000000200cc0003900000001033000390000000000d3004b0000101c0000c13d000010310000013d000000000007004b00000000030000190000102a0000613d0000000003a2034f000000000303043b000006520690027f0000065205600167000000000353016f000000000583019f0000103d0000013d0000060803000041000000000c00001900000000007b004b0000103c0000813d000000f80790018f000006520770027f00000652057001670000000006c600190000002406600039000000000662034f000000000606043b000000000556016f000000000053041b00000001058001bf000000000054041b000000cc06000039000000000306041a000005fe07300197000000000717019f000000000076041b000000a402200370000000000202043b000000cd06000039000000000026041b000000ce02000039000000000602041a00000651066001970000000f066001af000000000062041b000000800200003900000006080000290000000000280435000000d002000039000000000202041a000005b00220019700000080068000390000000000260435000000d102000039000000000202041a000000a0068000390000000000260435000000d202000039000000000202041a000000c0068000390000000000260435000000d302000039000000000202041a000000e0068000390000000000260435000000d402000039000000000202041a00000100068000390000000000260435000000d502000039000000000202041a00000120068000390000000000260435000000d602000039000000000202041a00000140068000390000000000260435000000d702000039000000000202041a00000180068000390000012007000039000000000076043500000160068000390000000000260435000000d802000039000000000702041a000000010870019000000001027002700000007f0220618f0000001f0020008c00000000060000390000000106002039000000000667013f00000001006001900000047d0000c13d0000000609000029000001a0069000390000000000260435000001c006900039000000000008004b000010930000613d000000d807000039000000000070043f000000000002004b0000000007000019000010980000613d000005f50800004100000000070000190000000009670019000000000a08041a0000000000a9043500000001088000390000002007700039000000000027004b0000108b0000413d000010980000013d00000651077001970000000000760435000000000002004b000000200700003900000000070060390000000002670019000000060620006a00000004070000290000000000670435000000010950019000000001075002700000007f0870018f000000000607001900000000060860190000001f0060008c000000000a000039000000010a002039000000000aa5013f0000000100a001900000047d0000c13d000000000a620436000000000009004b000010b00000c13d000006510450019700000000004a0435000000000008004b000000200220c0390000000005020019000010bf0000013d000000c00500043d0000000000450435000000c00400043d000000000074004b000010be0000813d000006080700004100000020044000390000000005240019000000000807041a00000000008504350000000107700039000000000064004b000010b60000413d000010bf0000013d000000000524001900000006070000290000006002700039000000c00400043d0000000f060000290000000000620435000000000234016f000000000112019f0000004002700039000000000012043500000000017500490000002001100039000005ad0010009c000005ad010080410000006001100210000005ad0070009c000005ad070080410000004002700210000000000121019f0000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f0000060a011001c70000800d0200003900000001030000390000060b0400004116af16a50000040f0000000100200190000001130000613d000000c00200043d000f00000002001d0000000d0020006c0000116b0000813d0000000f010000290000000503100210000b000c0030002d00000002040003670000000b01400360000000000101043b000005b00010009c000001130000213d000000000001004b000011b70000613d0000000a02300029000000000224034f000000000202043b000005b00020009c000001130000213d000000000002004b000011be0000613d0000000f06000029000000090060006c000011c20000813d00000008053000290000000503300029000000000634034f000000000354034f000000000303043b000000000406043b000000ff0040008c0000013c0000213d000000400500043d000e00000005001d0000060e0050009c0000076a0000213d0000000e060000290000006005600039000000400050043f0000004005600039000700000005001d00000000004504350000000002260436000600000002001d0000000000320435000000c00200043d0000000000120435000000cf01000039000000200010043f000000c00100043d000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000121019f0000060f011001c7000080100200003916af16aa0000040f0000000100200190000001130000613d0000000e020000290000000002020433000005b002200197000000000101043b000000000301041a000005fe03300197000000000223019f000000000021041b000000060200002900000000020204330000000103100039000000000023041b0000000201100039000000000201041a000006510220019700000007030000290000000003030433000000ff0330018f000000000232019f000000000021041b0000000b010000290000000201100367000000000101043b000e00000001001d000005b00010009c000001130000213d000000c00100043d0000000e020000290000000000210435000000cf01000039000000200010043f000000c00100043d000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000121019f0000060f011001c7000080100200003916af16aa0000040f0000000100200190000001130000613d000000000101043b000000400200043d0000000e030000290000000003320436000000000401041a000005b00440019700000000004304350000000103100039000000000303041a000000400420003900000000003404350000000201100039000000000101041a000000ff0110018f00000060032000390000000000130435000005ad0020009c000005ad0200804100000040012002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f00000610011001c70000800d020000390000000103000039000006110400004116af16a50000040f0000000100200190000001130000613d0000000f020000290000000102200039000010de0000013d000006120100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000000000001004b000004d00000613d00000613020000410000000000200443000000010110008a00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c70000800b0200003916af16aa0000040f0000000100200190000011b60000613d000000000101043b000005b001100197000000dd03000039000000000203041a000005fe02200197000000000112019f000000000013041b000000800100043d000005b0061001970000006501000039000000000201041a000005fe03200197000000000363019f000000000031041b0000000001000414000005b005200197000005ad0010009c000005ad01008041000000c0011002100000060a011001c70000800d020000390000000303000039000006140400004116af16a50000040f0000000100200190000001130000613d000000a00100043d000000000001004b000007710000613d000000c00100043d000000000201041a0000065302200197000000000021041b000000400100043d00000001030000390000000000310435000005ad0010009c000005ad0100804100000040011002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f000005b5011001c70000800d02000039000005b604000041000006370000013d000000000001042f000000400100043d000000440210003900000616030000410000000000320435000000240210003900000012030000390000061e0000013d000000400100043d00000044021000390000061503000041000009bc0000013d0000060c01000041000000000010043f0000003201000039000000040010043f0000060d01000041000016b100010430000006540010009c000011cd0000813d0000006001100039000000400010043f000000000001042d0000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b1000104300000001f0220003900000650022001970000000001120019000000000021004b00000000020000390000000102004039000005ed0010009c000011df0000213d0000000100200190000011df0000c13d000000400010043f000000000001042d0000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b10001043000000000430104340000000001320436000000000003004b000011f10000613d000000000200001900000000052100190000000006240019000000000606043300000000006504350000002002200039000000000032004b000011ea0000413d000000000231001900000000000204350000001f0230003900000650022001970000000001210019000000000001042d000005b0061001970000006501000039000000000201041a000005fe03200197000000000363019f000000000031041b0000000001000414000005b005200197000005ad0010009c000005ad01008041000000c0011002100000060a011001c70000800d020000390000000303000039000006140400004116af16a50000040f00000001002001900000120a0000613d000000000001042d0000000001000019000016b1000104300003000000000002000300000002001d000000400b00043d000006550200004100000000052b04360000000003000414000005b002100197000000040020008c0000121a0000c13d0000000103000031000000a00030008c000000a0040000390000000004034019000012480000013d000100000005001d000005ad00b0009c000005ad0100004100000000010b40190000004001100210000005ad0030009c000005ad03008041000000c003300210000000000113019f00000601011001c700020000000b001d16af16aa0000040f000000020b0000290000006003100270000005ad03300197000000a00030008c000000a00400003900000000040340190000001f0640018f000000e00740019000000000057b0019000012360000613d000000000801034f00000000090b0019000000008a08043c0000000009a90436000000000059004b000012320000c13d000000000006004b000012430000613d000000000771034f0000000306600210000000000805043300000000086801cf000000000868022f000000000707043b0000010006600089000000000767022f00000000066701cf000000000686019f0000000000650435000100000003001f00030000000103550000000100200190000012ad0000613d00000001050000290000001f01400039000001e00210018f0000000001b20019000000000021004b00000000020000390000000102004039000005ed0010009c0000127d0000213d00000001002001900000127d0000c13d000000400010043f0000009f0030008c0000127b0000a13d00000000020b0433000006560020009c0000127b0000213d0000008002b000390000000002020433000006560020009c0000127b0000213d0000000003050433000005ee0030009c000012830000213d000000000003004b000012830000613d000000000002004b000012890000613d000200000003001d0000006002b000390000000002020433000100000002001d000000000002004b0000128f0000613d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000012950000613d000000000101043b000000030210006c0000000201000029000012960000413d000000010020006b0000129c0000a13d000000000001042d0000000001000019000016b1000104300000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b10001043000000044021000390000065a03000041000000000032043500000024021000390000000e03000039000012a20000013d00000044021000390000065903000041000000000032043500000024021000390000000b03000039000012a20000013d00000044021000390000065803000041000000000032043500000024021000390000001203000039000012a20000013d000000000001042f0000060c01000041000000000010043f0000001101000039000000040010043f0000060d01000041000016b100010430000000400100043d00000044021000390000065703000041000000000032043500000024021000390000001c030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b1000104300000001f0530018f000005af06300198000000400200043d0000000004620019000012b80000613d000000000701034f0000000008020019000000007907043c0000000008980436000000000048004b000012b40000c13d000000000005004b000012c50000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000006001300210000005ad0020009c000005ad020080410000004002200210000000000112019f000016b100010430000006120100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f0000000100200190000012e90000613d000000000101043b000000000001004b000012ea0000613d00000613020000410000000000200443000000010110008a00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c70000800b0200003916af16aa0000040f0000000100200190000012e90000613d000000000101043b000005b001100197000000000001042d000000000001042f0000060c01000041000000000010043f0000001101000039000000040010043f0000060d01000041000016b100010430000000d702000039000000000202041a000000000002004b000012fd0000613d000005b002200198000012ff0000613d000000dd03000039000000000303041a000000000113013f000005b001100197000005b00220012900000000012100d9000000000001042d0000000001000019000000000001042d0000060c01000041000000000010043f0000001201000039000000040010043f0000060d01000041000016b1000104300002000000000002000200000002001d000100000001001d00000627010000410000000000100443000000000100041000000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c70000800a0200003916af16aa0000040f0000000100200190000013650000613d000000000101043b0000000203000029000000000031004b000013660000413d00000000010004140000000102000029000005b004200197000000040040008c000013230000c13d00000001020000390000000101000031000000000001004b000013340000c13d0000135c0000013d000005ad0010009c000005ad01008041000000c001100210000000000003004b0000132c0000613d0000060a011001c7000080090200003900000000050000190000132d0000013d000000000204001916af16a50000040f00030000000103550000006001100270000105ad0010019d000005ad01100197000000000001004b0000135c0000613d000006480010009c0000135f0000813d0000001f0410003900000650044001970000003f044000390000065005400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000005ed0050009c0000135f0000213d00000001006001900000135f0000c13d000000400050043f000000000614043600000650031001980000001f0410018f000000000136001900000003050003670000134f0000613d000000000705034f000000007807043c0000000006860436000000000016004b0000134b0000c13d000000000004004b0000135c0000613d000000000335034f0000000304400210000000000501043300000000054501cf000000000545022f000000000303043b0000010004400089000000000343022f00000000034301cf000000000353019f00000000003104350000000100200190000013770000613d000000000001042d0000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b100010430000000000001042f000000400100043d00000044021000390000065d03000041000000000032043500000024021000390000001d030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b100010430000000400100043d00000064021000390000065b03000041000000000032043500000044021000390000065c03000041000000000032043500000024021000390000003a030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad010080410000004001100210000005b4011001c7000016b1000104300003000000000002000200000002001d000300000001001d0000000001000411000000000010043f000000dc01000039000000200010043f0000000001000414000005ad0010009c000005ad01008041000000c0011002100000060f011001c7000080100200003916af16aa0000040f0000000100200190000013e80000613d000000000101043b000000000101041a0000000302000029000000000021001a000013ea0000413d0000000001210019000000020010006c000013f00000213d000000db01000039000000000301041a000000000023001a000013ea0000413d0000000004230019000000d201000039000000000101041a000000000014004b000013f40000213d000000d401000039000000000101041a000000000021004b000013fb0000213d000000d901000039000000000201041a000000010220003a000013ea0000613d000100000004001d000200000003001d000000000021041b0000000001000411000000000010043f000000dc01000039000000200010043f0000000001000414000005ad0010009c000005ad01008041000000c0011002100000060f011001c7000080100200003916af16aa0000040f0000000100200190000013e80000613d000000000101043b000000000101041a000000000001004b000000db0300003900000002040000290000000105000029000013d00000c13d000000da01000039000000000201041a000000010220003a000013ea0000613d000000000021041b000000000045004b000013ea0000413d000000000053041b0000000001000411000000000010043f000000dc01000039000000200010043f0000000001000414000005ad0010009c000005ad01008041000000c0011002100000060f011001c7000080100200003916af16aa0000040f0000000100200190000013e80000613d000000000101043b000000000201041a0000000303000029000000000032001a000013ea0000413d0000000002320019000000000021041b000000000001042d0000000001000019000016b1000104300000060c01000041000000000010043f0000001101000039000000040010043f0000060d01000041000016b100010430000000400100043d00000044021000390000066003000041000013f70000013d000000400100043d00000044021000390000065f03000041000000000032043500000024021000390000001b03000039000014010000013d000000400100043d00000044021000390000065e030000410000000000320435000000240210003900000016030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b1000104300004000000000002000000400400043d000006610040009c000014cf0000813d000005b0051001970000004001400039000000400010043f0000002001400039000006620300004100000000003104350000002001000039000000000014043500000000230204340000000001000414000000040050008c000014470000c13d0000000101000032000014820000613d000005ed0010009c000014cf0000213d0000001f0310003900000650033001970000003f033000390000065003300197000000400a00043d00000000033a00190000000000a3004b00000000040000390000000104004039000005ed0030009c000014cf0000213d0000000100400190000014cf0000c13d000000400030043f00000000051a043600000650021001980000001f0310018f00000000012500190000000304000367000014390000613d000000000604034f000000006706043c0000000005750436000000000015004b000014350000c13d000000000003004b000014830000613d000000000224034f0000000303300210000000000401043300000000043401cf000000000434022f000000000202043b0000010003300089000000000232022f00000000023201cf000000000242019f0000000000210435000014830000013d000200000004001d000005ad0030009c000005ad030080410000006003300210000005ad0020009c000005ad020080410000004002200210000000000223019f000005ad0010009c000005ad01008041000000c001100210000000000112019f000100000005001d000000000205001916af16a50000040f00030000000103550000006003100270000105ad0030019d000005ad043001980000149a0000613d0000001f03400039000005ae033001970000003f033000390000066303300197000000400a00043d00000000033a00190000000000a3004b00000000050000390000000105004039000005ed0030009c000014cf0000213d0000000100500190000014cf0000c13d000000400030043f0000001f0540018f00000000034a0436000005af064001980000000004630019000014740000613d000000000701034f0000000008030019000000007907043c0000000008980436000000000048004b000014700000c13d000000000005004b0000149c0000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000149c0000013d000000600a0000390000000002000415000000040220008a000000050220021000000000010a0433000000000001004b000014a40000c13d00020000000a001d000005ef010000410000000000100443000000040100003900000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000015010000613d0000000002000415000000040220008a000014b70000013d000000600a000039000000800300003900000000010a04330000000100200190000014eb0000613d0000000002000415000000030220008a0000000502200210000000000001004b000014a70000613d000000050220027000000000020a001f000014c10000013d00020000000a001d000005ef010000410000000000100443000000010100002900000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000015010000613d0000000002000415000000030220008a0000000502200210000000000101043b000000000001004b000000020a000029000015020000613d00000000010a0433000000050220027000000000020a001f000000000001004b000014ce0000613d000005ee0010009c000014d50000213d000000200010008c000014d50000413d0000002001a000390000000001010433000000000001004b0000000002000039000000010200c039000000000021004b000014d50000c13d000000000001004b000014d70000613d000000000001042d0000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b1000104300000000001000019000016b100010430000000400100043d00000064021000390000066403000041000000000032043500000044021000390000066503000041000000000032043500000024021000390000002a030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad010080410000004001100210000005b4011001c7000016b100010430000000000001004b000015130000c13d000000400300043d000100000003001d000005b30100004100000000001304350000000401300039000000200200003900000000002104350000002402300039000000020100002916af11e50000040f00000001020000290000000001210049000005ad0010009c000005ad01008041000005ad0020009c000005ad0200804100000060011002100000004002200210000000000121019f000016b100010430000000000001042f000000400100043d00000044021000390000066603000041000000000032043500000024021000390000001d030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b100010430000005ad0030009c000005ad030080410000004002300210000005ad0010009c000005ad010080410000006001100210000000000121019f000016b1000104300003000000000002000100000002001d000200000001001d0000009701000039000000000101041a000005ef020000410000000000200443000005b001100197000300000001001d00000004001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f0011001c7000080020200003916af16aa0000040f0000000100200190000015590000613d000000000101043b000000000001004b0000155a0000613d000000400500043d000006670100004100000000001504350000000201000029000005b0011001970000000402500039000000000012043500000000010004140000000304000029000000040040008c000015550000613d000005ad0050009c000200000005001d000005ad0200004100000000020540190000004002200210000005ad0010009c000005ad01008041000000c001100210000000000121019f0000000103000029000000000003004b0000154c0000613d00000647011001c7000080090200003900000000050000190000154e0000013d0000060d011001c7000000000204001916af16a50000040f00030000000103550000006003100270000105ad0030019d00000001002001900000000205000029000015620000613d000006480050009c0000155c0000813d000000400050043f000000000001042d000000000001042f0000000001000019000016b1000104300000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b100010430000005ad033001970000001f0530018f000005af06300198000000400200043d00000000046200190000156e0000613d000000000701034f0000000008020019000000007907043c0000000008980436000000000048004b0000156a0000c13d000000000005004b0000157b0000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000006001300210000005ad0020009c000005ad020080410000004002200210000000000121019f000016b1000104300003000000000002000300000005001d000200000001001d00000000010004100000006005100210000000400100043d00000020061000390000000000560435000000600220021000000034051000390000000000250435000100000004001d000000c002400210000000680510003900000000002504350000004802100039000000000032043500000050020000390000000000210435000006680010009c000016100000813d0000008002100039000000400020043f000005ad0060009c000005ad0600804100000040026002100000000001010433000005ad0010009c000005ad010080410000006001100210000000000121019f0000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f0000060a011001c7000080100200003916af16aa0000040f00000001002001900000160e0000613d000000000101043b0000066902000041000000000020043f0000001c0010043f0000000001000414000005ad0010009c000005ad01008041000000c0011002100000066a011001c7000080100200003916af16aa0000040f00000001002001900000160e0000613d000000400200043d000000000101043b00000003050000290000000034050434000000410040008c000016160000c13d000000400450003900000000040404330000066c0040009c000016260000213d0000006005500039000000000505043300000000030304330000006006200039000000000046043500000040042000390000000000340435000000f803500270000000200420003900000000003404350000000000120435000000000000043f000005ad0020009c000005ad0200804100000040012002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f0000066d011001c7000000010200003916af16aa0000040f0000006003100270000005ad03300197000000200030008c000000200400003900000000040340190000001f0540018f0000002004400190000015e60000613d000000000601034f0000000007000019000000006806043c0000000007870436000000000047004b000015e20000c13d000000000005004b000015f30000613d000000000641034f0000000305500210000000000704043300000000075701cf000000000757022f000000000606043b0000010005500089000000000656022f00000000055601cf000000000575019f0000000000540435000100000003001f00030000000103550000000100200190000016390000613d000000000100043d000005b000100198000016570000613d000000020110014f000005b0001001980000165b0000c13d000005f60100004100000000001004430000000001000414000005ad0010009c000005ad01008041000000c001100210000005f7011001c70000800b0200003916af16aa0000040f00000001002001900000165f0000613d0000000102000029000005ed02200197000000000101043b000000000021004b000016600000813d000000000001042d0000000001000019000016b1000104300000060c01000041000000000010043f0000004101000039000000040010043f0000060d01000041000016b10001043000000044012000390000066b03000041000000000031043500000024012000390000001f030000390000000000310435000005b3010000410000000000120435000000040120003900000020030000390000000000310435000005ad0020009c000005ad02008041000000400120021000000603011001c7000016b100010430000000640120003900000671030000410000000000310435000000440120003900000672030000410000000000310435000000240120003900000022030000390000000000310435000005b3010000410000000000120435000000040120003900000020030000390000000000310435000005ad0020009c000005ad020080410000004001200210000005b4011001c7000016b1000104300000001f0530018f000005af06300198000000400200043d0000000004620019000016440000613d000000000701034f0000000008020019000000007907043c0000000008980436000000000048004b000016400000c13d000000000005004b000016510000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000006001300210000005ad0020009c000005ad020080410000004002200210000000000112019f000016b100010430000000400100043d00000044021000390000067003000041000016630000013d000000400100043d00000044021000390000066e03000041000016630000013d000000000001042f000000400100043d00000044021000390000066f030000410000000000320435000000240210003900000018030000390000000000320435000005b3020000410000000000210435000000040210003900000020030000390000000000320435000005ad0010009c000005ad01008041000000400110021000000603011001c7000016b100010430000000000001042f000005ad0010009c000005ad010080410000004001100210000005ad0020009c000005ad020080410000006002200210000000000112019f0000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f0000060a011001c7000080100200003916af16aa0000040f0000000100200190000016850000613d000000000101043b000000000001042d0000000001000019000016b10001043000000000050100190000000000200443000000050030008c000016950000413d000000040100003900000000020000190000000506200210000000000664001900000005066002700000000006060031000000000161043a0000000102200039000000000031004b0000168d0000413d000005ad0030009c000005ad0300804100000060013002100000000002000414000005ad0020009c000005ad02008041000000c002200210000000000112019f00000673011001c7000000000205001916af16aa0000040f0000000100200190000016a40000613d000000000101043b000000000001042d000000000001042f000016a8002104210000000102000039000000000001042d0000000002000019000000000001042d000016ad002104230000000102000039000000000001042d0000000002000019000000000001042d000016af00000432000016b00001042e000016b1000104300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000001ffffffe000000000000000000000000000000000000000000000000000000000ffffffe0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff616c697a696e6700000000000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320696e69746908c379a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000000000000000000000002000000000000000000000000000000000000200000000000000000000000007f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498729563fd224b9a644af3cb647d3cb666ff093c97c2e7f1c6cefe51ee5bea587e00000002000000000000000000000000000000800000010000000000000000000000000000000000000000000000000000000000000000000000000092a85fdd00000000000000000000000000000000000000000000000000000000b4bd9e2600000000000000000000000000000000000000000000000000000000e2982c2000000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000ffa1ad7400000000000000000000000000000000000000000000000000000000e2982c2100000000000000000000000000000000000000000000000000000000e6064de300000000000000000000000000000000000000000000000000000000c32429db00000000000000000000000000000000000000000000000000000000c32429dc00000000000000000000000000000000000000000000000000000000c3b88b4200000000000000000000000000000000000000000000000000000000b4bd9e2700000000000000000000000000000000000000000000000000000000be8423de00000000000000000000000000000000000000000000000000000000a6b84e3000000000000000000000000000000000000000000000000000000000ab803a7500000000000000000000000000000000000000000000000000000000ab803a7600000000000000000000000000000000000000000000000000000000b421520700000000000000000000000000000000000000000000000000000000a6b84e3100000000000000000000000000000000000000000000000000000000a9e0c9c30000000000000000000000000000000000000000000000000000000092a85fde000000000000000000000000000000000000000000000000000000009d326b3d00000000000000000000000000000000000000000000000000000000a04748f6000000000000000000000000000000000000000000000000000000006ec0bc4900000000000000000000000000000000000000000000000000000000826b90f1000000000000000000000000000000000000000000000000000000008dbc8342000000000000000000000000000000000000000000000000000000008dbc83430000000000000000000000000000000000000000000000000000000090ced42100000000000000000000000000000000000000000000000000000000826b90f2000000000000000000000000000000000000000000000000000000008da5cb5b000000000000000000000000000000000000000000000000000000006ec0bc4a00000000000000000000000000000000000000000000000000000000715018a60000000000000000000000000000000000000000000000000000000079502c550000000000000000000000000000000000000000000000000000000031ab05170000000000000000000000000000000000000000000000000000000047535d7a0000000000000000000000000000000000000000000000000000000047535d7b000000000000000000000000000000000000000000000000000000004cc233a80000000000000000000000000000000000000000000000000000000031ab05180000000000000000000000000000000000000000000000000000000031b3eb94000000000000000000000000000000000000000000000000000000001a203c4f000000000000000000000000000000000000000000000000000000001be19560000000000000000000000000000000000000000000000000000000002ddbd13a342e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000010000000100000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084000000e000000000000000000000000000000000000000000000000000000020000000e00000000000000000e3a9db1a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000e0000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000e00000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c726561ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000fffffffffffffedf5320ad99a619a90804cd2efe3a5cf0ac1ac5c41ad9ff2c61cf699efdad771096796b89b91644bc98cd93958e4c9038275d622183e25ac5af08cc6b5d95539132020000020000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000f48657000000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000ffffffffffffff7b010000b735cd61ab80da8bff6c846bccb40a7fbdf3d63d2f1d523fdcdced5a099c4d535bdea7cd8a978f128b93471df48c7dbab89d703809115bdc118c235bfd0200000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000008129fc1c0000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000746f6b656e20616e64206f7261636c65206c656e6774687320213d00000000000000000000000000000000000000000000000064000000000000000000000000746f6b656e20616e6420646563696d616c73206c656e6774687320213d0000008000000000000000000000000000000000000000000000000000000000000000acdf526659e656f7fb32d101c5a30f53e53a3be52600d39e309661025288ef6a58317c92fcd4d409d481df68571f5927514cabfa52ead8e1692c4fe775e2f905a7ce836d032b2bf62b7e2097a8e0a6d8aeb35405ad15271e96d3b0188a1d06fb58317c92fcd4d409d481df68571f5927514cabfa52ead8e1692c4fe775e2f9040200000000000000000000000000000000000000000000000000000000000000b08510a4a112663ff701fcf43686a47fd456cb1a471dd63d662b73612cf700b34e487b71000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff9f02000000000000000000000000000000000000400000000000000000000000000200000000000000000000000000000000000080000000000000000000000000e9b592b561a1c20f7ad5ea0af6d767fa74bc9fc3f038c70f1718f191517ed94842cbb15ccdc3cad6266b0e7a08c0454b23bf29dc2df74b6f3c209e9336465bd180b41246c05cbb406f874e82aa2faf7db11bba9792fe09929e56ef1eee2c2da38be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0746f6b656e206f7261636c65203d3d20300000000000000000000000000000007061796d656e7420746f6b656e203d3d203000000000000000000000000000006e6174697665206f7261636c65203d3d203000000000000000000000000000006e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f742069000000000000000000000000000000000000000000000000ffffffffffffff1f938b5f3299a1f3b18e458564efbb950733226014eece26fae19012d850b48d83310ab089e4439a4c15d089f94afb7896ff553aecb10793d0ab882de59d99a32e02000002000000000000000000000000000000440000000000000000000000006afb5ced000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000e9ed68b00000000000000000000000000000000000000000000000000000000b31c8781000000000000000000000000000000000000000000000000000000004ccb20c00000000000000000000000000000000000000000000000000000000023b872dd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff5f75a85e7be0265abefef113ee168a0d751385a985c3a37920ae97ae192d2eadb4696e76616c6964207061796d656e7420746f6b656e00000000000000000000009cc7f708afc65944829bd487b90b72536b1951864fbfc14e125fc972a6507f395472616e73666572206661696c65642e000000000000000000000000000000003b381fdfc0e2729a70e8b26ae2397e9014f703a8235b557f5581c4ed47280fd24d75737420627579207769746820616e20454f4100000000000000000000000073616c6520686173206e6f74207374617274656420796574000000000000000073616c652068617320656e64656400000000000000000000000000000000000073616c6520627579206c696d69742072656163686564000000000000000000006e6f7420796f7572207475726e20796574000000000000000000000000000000666565207061796d656e742062656c6f77206d696e696d756d000000000000005265656e7472616e637947756172643a207265656e7472616e742063616c6c006e6174697665207061796d656e74732064697361626c656400000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffdff73616c65206973206f7665723a2063616e6e6f74207570617465000000000000746172740000000000000000000000000000000000000000000000000000000065646974696e672073616c654d6178696d756d2061667465722073616c65207300000000000000000000000000000000000000000000000000000000f48657010000000000000000000000000000000000000000000000000000000000093a816178517565756554696d6500000000000000000000000000000000000000000073616c65206d757374206265206f70656e20666f72206174206c65617374206d4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572acdf526659e656f7fb32d101c5a30f53e53a3be52600d39e309661025288ef69f19e3240beb82d0dfa0a35ed50201f0ac38ea92b9b29450527293aa8ccd0979b70757263686173654d696e696d756d203e20757365724d6178696d756d000000757365724d6178696d756d203e2073616c654d6178696d756d00000000000000757365724d6178696d756d203d3d20300000000000000000000000000000000073616c654d6178696d756d203d3d203000000000000000000000000000000000726563697069656e74203d3d20616464726573732830290000000000000000006d61782071756575652074696d65203e20363034383030202831207765656b29656e64203e203431303234343438303020284a616e20312032313030290000007374617274203e203431303234343438303020284a616e20312032313030290051cff8d900000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000064000000e000000000000000000200000000000000000000000000000000000020000000e00000000000000000bfbef9c12fc2cf7df9f0b7679d5863e31a093e9c2c8d5dd9de1ddca31a0748284469737472696275746f72203d3d20616464726573732830290000000000000070a0823100000000000000000000000000000000000000000000000000000000a9059cbb00000000000000000000000000000000000000000000000000000000f4a44a7f605c4971a27bcecb448108e6328b7fad34fab5bff4f69377294b826dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff000000000000000000000000000000000000000000000000ffffffffffffffa0feaf968c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffff7374616c652070726963652064756520746f2068656172746265617400000000726f756e64206e6f7420636f6d706c6574650000000000000000000000000000616e73776572203d3d20300000000000000000000000000000000000000000006e656761746976652070726963650000000000000000000000000000000000006563697069656e74206d61792068617665207265766572746564000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c2072416464726573733a20696e73756666696369656e742062616c616e6365000000707572636861736520756e646572206d696e696d756d00000000000000000000707572636861736520657863656564732073616c65206c696d697400000000007075726368617365206578636565647320796f7572206c696d69740000000000000000000000000000000000000000000000000000000000ffffffffffffffc05361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656400000000000000000000000000000000000000000000000000000003ffffffe06f742073756363656564000000000000000000000000000000000000000000005361666545524332303a204552433230206f7065726174696f6e20646964206e416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000f340fa0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffff8019457468657265756d205369676e6564204d6573736167653a0a333200000000020000000000000000000000000000000000003c00000000000000000000000045434453413a20696e76616c6964207369676e6174757265206c656e677468007fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a00000000000000000000000000000000000000080000000000000000000000000616363657373207369676e617475726520696e76616c69640000000000000000616363657373207369676e61747572652065787069726564000000000000000045434453413a20696e76616c6964207369676e61747572650000000000000000756500000000000000000000000000000000000000000000000000000000000045434453413a20696e76616c6964207369676e6174757265202773272076616c0200000200000000000000000000000000000000000000000000000000000000570e0aebf0b17686d0af2f672e030e023ba4003b778bb2cb9123441879c6e8d8", + "entries": [ + { + "constructorArgs": [ + "0xC40D34317fF3685E9Eb18783CA992bD5566073eB" + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [ + "0x000200000000000200050000000000020000006003100270000000860330019700010000003103550000008004000039000000400040043f0000000100200190000000270000c13d000000040030008c0000015e0000413d000000000201043b000000e002200270000000880020009c0000002f0000a13d000000890020009c000000530000213d0000008c0020009c000000cb0000613d0000008d0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d000000000010043f0000006501000039000000200010043f0000000001000019021501fa0000040f000000000101041a000000800010043f0000009b01000041000002160001042e0000000001000416000000000001004b0000015e0000c13d0000002001000039000001000010044300000120000004430000008701000041000002160001042e0000008e0020009c000000880000613d0000008f0020009c000000f10000613d000000900020009c0000015e0000c13d0000000001000416000000000001004b0000015e0000c13d0000000003000415000000050330008a0000000503300210000000000200041a0000ff00012001900000010f0000c13d0000000003000415000000040330008a0000000503300210000000ff002001900000010f0000c13d000000a10120019700000101011001bf0000000002000019000000000010041b0000ff0000100190000001320000c13d000000400100043d0000006402100039000000a60300004100000000003204350000004402100039000000a703000041000000000032043500000024021000390000002b030000390000016d0000013d0000008a0020009c000000d40000613d0000008b0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000301041a0000000002000416000000000032001a000001de0000413d0000000003320019000000000031041b000000400100043d0000000000210435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000020300003900000094040000410000000305000029000001590000013d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000201041a000200000002001d000000000001041b000000aa010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800a02000039021502100000040f0000000100200190000001600000613d000000000101043b0000000203000029000000000031004b0000017b0000813d000000400100043d0000004402100039000000b003000041000000000032043500000024021000390000001d03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000b1011001c700000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000101041a0000009101100197000000800010043f0000009b01000041000002160001042e000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009100100198000001780000c13d0000009701000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000009801000041000000c40010043f0000009901000041000000e40010043f0000009a0100004100000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000201041a00000091052001970000000003000411000000000035004b000001060000c13d000000a202200197000000000021041b0000000001000414000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410000000006000019000001590000013d0000009701000041000000800010043f0000002001000039000000840010043f000000a40010043f000000a801000041000000c40010043f000000a9010000410000021700010430000300000003001d000100000001001d000200000002001d0000009c010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800202000039021502100000040f0000000100200190000001600000613d000000000101043b000000000001004b000001610000c13d0000000202000029000000ff0120018f000000010010008c00000003010000290000000501100270000000000100003f000000010100603f000001640000c13d000000010000006b000000430000613d000000b201200197000000010200003900000001011001bf000000000010041b0000ff0000100190000000490000613d000300000002001d000000000100041100000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f00000001002001900000015e0000613d000000030000006b0000015c0000c13d000000000200041a000000b301200197000000000010041b0000000103000039000000400100043d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000a5040000410215020b0000040f00000001002001900000015e0000613d0000000001000019000002160001042e00000000010000190000021700010430000000000001042f00000003010000290000000501100270000000000100003f000000400100043d00000064021000390000009e03000041000000000032043500000044021000390000009f03000041000000000032043500000024021000390000002e03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000a0011001c70000021700010430021501e40000040f0000000001000019000002160001042e00000000010004140000000302000029000000040020008c000001820000c13d00000001020000390000000001000031000001930000013d000000860010009c0000008601008041000000c001100210000000000003004b000001890000c13d00000003020000290000018d0000013d000000a3011001c70000800902000039000000030400002900000000050000190215020b0000040f000000020300002900010000000103550000006001100270000000860010019d0000008601100197000000000001004b000001a60000c13d000000400100043d0000000100200190000001ae0000613d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d020000390000000203000039000000af04000041000000860000013d000000ab0010009c000001b70000413d0000009501000041000000000010043f0000004101000039000000040010043f000000960100004100000217000104300000006402100039000000ad0300004100000000003204350000004402100039000000ae03000041000000000032043500000024021000390000003a030000390000016d0000013d0000001f04100039000000b4044001970000003f04400039000000b405400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000000ac0050009c000001a80000213d0000000100600190000001a80000c13d000000400050043f0000000006140436000000b4091001980000001f0410018f00000000019600190000000105000367000001d00000613d000000000705034f000000007807043c0000000006860436000000000016004b000001cc0000c13d000000000004004b000001950000613d000000000695034f0000000304400210000000000501043300000000054501cf000000000545022f000000000606043b0000010004400089000000000646022f00000000044601cf000000000454019f0000000000410435000001950000013d0000009501000041000000000010043f0000001101000039000000040010043f0000009601000041000002170001043000000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f0000000100200190000001f70000613d000000000001042d00000000010000190000021700010430000000000001042f0000000002000414000000860020009c0000008602008041000000c002200210000000860010009c00000086010080410000004001100210000000000121019f00000092011001c70000801002000039021502100000040f0000000100200190000002090000613d000000000101043b000000000001042d000000000100001900000217000104300000020e002104210000000102000039000000000001042d0000000002000019000000000001042d00000213002104230000000102000039000000000001042d0000000002000019000000000001042d0000021500000432000002160001042e000002170001043000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000f340fa01000000000000000000000000000000000000000000000000000000008da5cb5b00000000000000000000000000000000000000000000000000000000e3a9db1a0000000000000000000000000000000000000000000000000000000051cff8d900000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000008129fc1c000000000000000000000000ffffffffffffffffffffffffffffffffffffffff020000000000000000000000000000000000004000000000000000000000000002000000000000000000000000000000000000200000000000000000000000002da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c44e487b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000008c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000080000000000000000000000000000000000000000000000000000000200000008000000000000000001806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c7265610000000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e07f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024986e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f7420694f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657200000000000000000000000000000000000000640000008000000000000000009cc7f708afc65944829bd487b90b72536b1951864fbfc14e125fc972a6507f390000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff6563697069656e74206d61792068617665207265766572746564000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c20727084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5416464726573733a20696e73756666696369656e742062616c616e63650000000000000000000000000000000000000000000064000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000018b532e372f3b4020a6dd5ffb28e63fce9dab8e991740d8c60724ec4c1e2ce5" + ], + "address": "0x3760C5A9e2935f0662f92aDc3f2503Fedce2f791", + "txHash": "0x52199731d130069a48d2e6acbf05e4318432322e15257fc6ec05354f49304b60" + }, + { + "constructorArgs": [ + "0x1F0DD164f6b9fDfCB5CA4801dE5f9667349330d7" + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [ + "0x000200000000000200050000000000020000006003100270000000860330019700010000003103550000008004000039000000400040043f0000000100200190000000270000c13d000000040030008c0000015e0000413d000000000201043b000000e002200270000000880020009c0000002f0000a13d000000890020009c000000530000213d0000008c0020009c000000cb0000613d0000008d0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d000000000010043f0000006501000039000000200010043f0000000001000019021501fa0000040f000000000101041a000000800010043f0000009b01000041000002160001042e0000000001000416000000000001004b0000015e0000c13d0000002001000039000001000010044300000120000004430000008701000041000002160001042e0000008e0020009c000000880000613d0000008f0020009c000000f10000613d000000900020009c0000015e0000c13d0000000001000416000000000001004b0000015e0000c13d0000000003000415000000050330008a0000000503300210000000000200041a0000ff00012001900000010f0000c13d0000000003000415000000040330008a0000000503300210000000ff002001900000010f0000c13d000000a10120019700000101011001bf0000000002000019000000000010041b0000ff0000100190000001320000c13d000000400100043d0000006402100039000000a60300004100000000003204350000004402100039000000a703000041000000000032043500000024021000390000002b030000390000016d0000013d0000008a0020009c000000d40000613d0000008b0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000301041a0000000002000416000000000032001a000001de0000413d0000000003320019000000000031041b000000400100043d0000000000210435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000020300003900000094040000410000000305000029000001590000013d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000201041a000200000002001d000000000001041b000000aa010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800a02000039021502100000040f0000000100200190000001600000613d000000000101043b0000000203000029000000000031004b0000017b0000813d000000400100043d0000004402100039000000b003000041000000000032043500000024021000390000001d03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000b1011001c700000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000101041a0000009101100197000000800010043f0000009b01000041000002160001042e000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009100100198000001780000c13d0000009701000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000009801000041000000c40010043f0000009901000041000000e40010043f0000009a0100004100000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000201041a00000091052001970000000003000411000000000035004b000001060000c13d000000a202200197000000000021041b0000000001000414000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410000000006000019000001590000013d0000009701000041000000800010043f0000002001000039000000840010043f000000a40010043f000000a801000041000000c40010043f000000a9010000410000021700010430000300000003001d000100000001001d000200000002001d0000009c010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800202000039021502100000040f0000000100200190000001600000613d000000000101043b000000000001004b000001610000c13d0000000202000029000000ff0120018f000000010010008c00000003010000290000000501100270000000000100003f000000010100603f000001640000c13d000000010000006b000000430000613d000000b201200197000000010200003900000001011001bf000000000010041b0000ff0000100190000000490000613d000300000002001d000000000100041100000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f00000001002001900000015e0000613d000000030000006b0000015c0000c13d000000000200041a000000b301200197000000000010041b0000000103000039000000400100043d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000a5040000410215020b0000040f00000001002001900000015e0000613d0000000001000019000002160001042e00000000010000190000021700010430000000000001042f00000003010000290000000501100270000000000100003f000000400100043d00000064021000390000009e03000041000000000032043500000044021000390000009f03000041000000000032043500000024021000390000002e03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000a0011001c70000021700010430021501e40000040f0000000001000019000002160001042e00000000010004140000000302000029000000040020008c000001820000c13d00000001020000390000000001000031000001930000013d000000860010009c0000008601008041000000c001100210000000000003004b000001890000c13d00000003020000290000018d0000013d000000a3011001c70000800902000039000000030400002900000000050000190215020b0000040f000000020300002900010000000103550000006001100270000000860010019d0000008601100197000000000001004b000001a60000c13d000000400100043d0000000100200190000001ae0000613d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d020000390000000203000039000000af04000041000000860000013d000000ab0010009c000001b70000413d0000009501000041000000000010043f0000004101000039000000040010043f000000960100004100000217000104300000006402100039000000ad0300004100000000003204350000004402100039000000ae03000041000000000032043500000024021000390000003a030000390000016d0000013d0000001f04100039000000b4044001970000003f04400039000000b405400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000000ac0050009c000001a80000213d0000000100600190000001a80000c13d000000400050043f0000000006140436000000b4091001980000001f0410018f00000000019600190000000105000367000001d00000613d000000000705034f000000007807043c0000000006860436000000000016004b000001cc0000c13d000000000004004b000001950000613d000000000695034f0000000304400210000000000501043300000000054501cf000000000545022f000000000606043b0000010004400089000000000646022f00000000044601cf000000000454019f0000000000410435000001950000013d0000009501000041000000000010043f0000001101000039000000040010043f0000009601000041000002170001043000000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f0000000100200190000001f70000613d000000000001042d00000000010000190000021700010430000000000001042f0000000002000414000000860020009c0000008602008041000000c002200210000000860010009c00000086010080410000004001100210000000000121019f00000092011001c70000801002000039021502100000040f0000000100200190000002090000613d000000000101043b000000000001042d000000000100001900000217000104300000020e002104210000000102000039000000000001042d0000000002000019000000000001042d00000213002104230000000102000039000000000001042d0000000002000019000000000001042d0000021500000432000002160001042e000002170001043000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000f340fa01000000000000000000000000000000000000000000000000000000008da5cb5b00000000000000000000000000000000000000000000000000000000e3a9db1a0000000000000000000000000000000000000000000000000000000051cff8d900000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000008129fc1c000000000000000000000000ffffffffffffffffffffffffffffffffffffffff020000000000000000000000000000000000004000000000000000000000000002000000000000000000000000000000000000200000000000000000000000002da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c44e487b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000008c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000080000000000000000000000000000000000000000000000000000000200000008000000000000000001806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c7265610000000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e07f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024986e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f7420694f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657200000000000000000000000000000000000000640000008000000000000000009cc7f708afc65944829bd487b90b72536b1951864fbfc14e125fc972a6507f390000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff6563697069656e74206d61792068617665207265766572746564000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c20727084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5416464726573733a20696e73756666696369656e742062616c616e63650000000000000000000000000000000000000000000064000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000018b532e372f3b4020a6dd5ffb28e63fce9dab8e991740d8c60724ec4c1e2ce5" + ], + "address": "0x609Eb0E5b5Fa64d85CF2d225686881986136F3B3", + "txHash": "0x8b6093e3d8e9a385937ba8f08b68816cc42c2703855b2b9bee7aaec523092fa8" + }, + { + "constructorArgs": [ + "0x53222c34F7E8528e25E0070E0759414629425d95" + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [ + "0x000200000000000200050000000000020000006003100270000000860330019700010000003103550000008004000039000000400040043f0000000100200190000000270000c13d000000040030008c0000015e0000413d000000000201043b000000e002200270000000880020009c0000002f0000a13d000000890020009c000000530000213d0000008c0020009c000000cb0000613d0000008d0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d000000000010043f0000006501000039000000200010043f0000000001000019021501fa0000040f000000000101041a000000800010043f0000009b01000041000002160001042e0000000001000416000000000001004b0000015e0000c13d0000002001000039000001000010044300000120000004430000008701000041000002160001042e0000008e0020009c000000880000613d0000008f0020009c000000f10000613d000000900020009c0000015e0000c13d0000000001000416000000000001004b0000015e0000c13d0000000003000415000000050330008a0000000503300210000000000200041a0000ff00012001900000010f0000c13d0000000003000415000000040330008a0000000503300210000000ff002001900000010f0000c13d000000a10120019700000101011001bf0000000002000019000000000010041b0000ff0000100190000001320000c13d000000400100043d0000006402100039000000a60300004100000000003204350000004402100039000000a703000041000000000032043500000024021000390000002b030000390000016d0000013d0000008a0020009c000000d40000613d0000008b0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000301041a0000000002000416000000000032001a000001de0000413d0000000003320019000000000031041b000000400100043d0000000000210435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000020300003900000094040000410000000305000029000001590000013d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000201041a000200000002001d000000000001041b000000aa010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800a02000039021502100000040f0000000100200190000001600000613d000000000101043b0000000203000029000000000031004b0000017b0000813d000000400100043d0000004402100039000000b003000041000000000032043500000024021000390000001d03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000b1011001c700000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000101041a0000009101100197000000800010043f0000009b01000041000002160001042e000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009100100198000001780000c13d0000009701000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000009801000041000000c40010043f0000009901000041000000e40010043f0000009a0100004100000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000201041a00000091052001970000000003000411000000000035004b000001060000c13d000000a202200197000000000021041b0000000001000414000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410000000006000019000001590000013d0000009701000041000000800010043f0000002001000039000000840010043f000000a40010043f000000a801000041000000c40010043f000000a9010000410000021700010430000300000003001d000100000001001d000200000002001d0000009c010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800202000039021502100000040f0000000100200190000001600000613d000000000101043b000000000001004b000001610000c13d0000000202000029000000ff0120018f000000010010008c00000003010000290000000501100270000000000100003f000000010100603f000001640000c13d000000010000006b000000430000613d000000b201200197000000010200003900000001011001bf000000000010041b0000ff0000100190000000490000613d000300000002001d000000000100041100000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f00000001002001900000015e0000613d000000030000006b0000015c0000c13d000000000200041a000000b301200197000000000010041b0000000103000039000000400100043d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000a5040000410215020b0000040f00000001002001900000015e0000613d0000000001000019000002160001042e00000000010000190000021700010430000000000001042f00000003010000290000000501100270000000000100003f000000400100043d00000064021000390000009e03000041000000000032043500000044021000390000009f03000041000000000032043500000024021000390000002e03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000a0011001c70000021700010430021501e40000040f0000000001000019000002160001042e00000000010004140000000302000029000000040020008c000001820000c13d00000001020000390000000001000031000001930000013d000000860010009c0000008601008041000000c001100210000000000003004b000001890000c13d00000003020000290000018d0000013d000000a3011001c70000800902000039000000030400002900000000050000190215020b0000040f000000020300002900010000000103550000006001100270000000860010019d0000008601100197000000000001004b000001a60000c13d000000400100043d0000000100200190000001ae0000613d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d020000390000000203000039000000af04000041000000860000013d000000ab0010009c000001b70000413d0000009501000041000000000010043f0000004101000039000000040010043f000000960100004100000217000104300000006402100039000000ad0300004100000000003204350000004402100039000000ae03000041000000000032043500000024021000390000003a030000390000016d0000013d0000001f04100039000000b4044001970000003f04400039000000b405400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000000ac0050009c000001a80000213d0000000100600190000001a80000c13d000000400050043f0000000006140436000000b4091001980000001f0410018f00000000019600190000000105000367000001d00000613d000000000705034f000000007807043c0000000006860436000000000016004b000001cc0000c13d000000000004004b000001950000613d000000000695034f0000000304400210000000000501043300000000054501cf000000000545022f000000000606043b0000010004400089000000000646022f00000000044601cf000000000454019f0000000000410435000001950000013d0000009501000041000000000010043f0000001101000039000000040010043f0000009601000041000002170001043000000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f0000000100200190000001f70000613d000000000001042d00000000010000190000021700010430000000000001042f0000000002000414000000860020009c0000008602008041000000c002200210000000860010009c00000086010080410000004001100210000000000121019f00000092011001c70000801002000039021502100000040f0000000100200190000002090000613d000000000101043b000000000001042d000000000100001900000217000104300000020e002104210000000102000039000000000001042d0000000002000019000000000001042d00000213002104230000000102000039000000000001042d0000000002000019000000000001042d0000021500000432000002160001042e000002170001043000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000f340fa01000000000000000000000000000000000000000000000000000000008da5cb5b00000000000000000000000000000000000000000000000000000000e3a9db1a0000000000000000000000000000000000000000000000000000000051cff8d900000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000008129fc1c000000000000000000000000ffffffffffffffffffffffffffffffffffffffff020000000000000000000000000000000000004000000000000000000000000002000000000000000000000000000000000000200000000000000000000000002da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c44e487b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000008c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000080000000000000000000000000000000000000000000000000000000200000008000000000000000001806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c7265610000000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e07f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024986e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f7420694f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657200000000000000000000000000000000000000640000008000000000000000009cc7f708afc65944829bd487b90b72536b1951864fbfc14e125fc972a6507f390000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff6563697069656e74206d61792068617665207265766572746564000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c20727084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5416464726573733a20696e73756666696369656e742062616c616e63650000000000000000000000000000000000000000000064000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000018b532e372f3b4020a6dd5ffb28e63fce9dab8e991740d8c60724ec4c1e2ce5" + ], + "address": "0x281936646C742200D31Aa3632431bb4DC337Ca6C", + "txHash": "0xc32925b41a4fa4039c6b4f71893305af399a28428298c59e8b729c46b78ede34" + }, + { + "constructorArgs": [ + "0xf57a10fdBd6BA630d292ECc79a825Db4d9265c8B" + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [ + "0x000200000000000200050000000000020000006003100270000000860330019700010000003103550000008004000039000000400040043f0000000100200190000000270000c13d000000040030008c0000015e0000413d000000000201043b000000e002200270000000880020009c0000002f0000a13d000000890020009c000000530000213d0000008c0020009c000000cb0000613d0000008d0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d000000000010043f0000006501000039000000200010043f0000000001000019021501fa0000040f000000000101041a000000800010043f0000009b01000041000002160001042e0000000001000416000000000001004b0000015e0000c13d0000002001000039000001000010044300000120000004430000008701000041000002160001042e0000008e0020009c000000880000613d0000008f0020009c000000f10000613d000000900020009c0000015e0000c13d0000000001000416000000000001004b0000015e0000c13d0000000003000415000000050330008a0000000503300210000000000200041a0000ff00012001900000010f0000c13d0000000003000415000000040330008a0000000503300210000000ff002001900000010f0000c13d000000a10120019700000101011001bf0000000002000019000000000010041b0000ff0000100190000001320000c13d000000400100043d0000006402100039000000a60300004100000000003204350000004402100039000000a703000041000000000032043500000024021000390000002b030000390000016d0000013d0000008a0020009c000000d40000613d0000008b0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000301041a0000000002000416000000000032001a000001de0000413d0000000003320019000000000031041b000000400100043d0000000000210435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000020300003900000094040000410000000305000029000001590000013d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000201041a000200000002001d000000000001041b000000aa010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800a02000039021502100000040f0000000100200190000001600000613d000000000101043b0000000203000029000000000031004b0000017b0000813d000000400100043d0000004402100039000000b003000041000000000032043500000024021000390000001d03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000b1011001c700000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000101041a0000009101100197000000800010043f0000009b01000041000002160001042e000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009100100198000001780000c13d0000009701000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000009801000041000000c40010043f0000009901000041000000e40010043f0000009a0100004100000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000201041a00000091052001970000000003000411000000000035004b000001060000c13d000000a202200197000000000021041b0000000001000414000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410000000006000019000001590000013d0000009701000041000000800010043f0000002001000039000000840010043f000000a40010043f000000a801000041000000c40010043f000000a9010000410000021700010430000300000003001d000100000001001d000200000002001d0000009c010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800202000039021502100000040f0000000100200190000001600000613d000000000101043b000000000001004b000001610000c13d0000000202000029000000ff0120018f000000010010008c00000003010000290000000501100270000000000100003f000000010100603f000001640000c13d000000010000006b000000430000613d000000b201200197000000010200003900000001011001bf000000000010041b0000ff0000100190000000490000613d000300000002001d000000000100041100000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f00000001002001900000015e0000613d000000030000006b0000015c0000c13d000000000200041a000000b301200197000000000010041b0000000103000039000000400100043d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000a5040000410215020b0000040f00000001002001900000015e0000613d0000000001000019000002160001042e00000000010000190000021700010430000000000001042f00000003010000290000000501100270000000000100003f000000400100043d00000064021000390000009e03000041000000000032043500000044021000390000009f03000041000000000032043500000024021000390000002e03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000a0011001c70000021700010430021501e40000040f0000000001000019000002160001042e00000000010004140000000302000029000000040020008c000001820000c13d00000001020000390000000001000031000001930000013d000000860010009c0000008601008041000000c001100210000000000003004b000001890000c13d00000003020000290000018d0000013d000000a3011001c70000800902000039000000030400002900000000050000190215020b0000040f000000020300002900010000000103550000006001100270000000860010019d0000008601100197000000000001004b000001a60000c13d000000400100043d0000000100200190000001ae0000613d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d020000390000000203000039000000af04000041000000860000013d000000ab0010009c000001b70000413d0000009501000041000000000010043f0000004101000039000000040010043f000000960100004100000217000104300000006402100039000000ad0300004100000000003204350000004402100039000000ae03000041000000000032043500000024021000390000003a030000390000016d0000013d0000001f04100039000000b4044001970000003f04400039000000b405400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000000ac0050009c000001a80000213d0000000100600190000001a80000c13d000000400050043f0000000006140436000000b4091001980000001f0410018f00000000019600190000000105000367000001d00000613d000000000705034f000000007807043c0000000006860436000000000016004b000001cc0000c13d000000000004004b000001950000613d000000000695034f0000000304400210000000000501043300000000054501cf000000000545022f000000000606043b0000010004400089000000000646022f00000000044601cf000000000454019f0000000000410435000001950000013d0000009501000041000000000010043f0000001101000039000000040010043f0000009601000041000002170001043000000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f0000000100200190000001f70000613d000000000001042d00000000010000190000021700010430000000000001042f0000000002000414000000860020009c0000008602008041000000c002200210000000860010009c00000086010080410000004001100210000000000121019f00000092011001c70000801002000039021502100000040f0000000100200190000002090000613d000000000101043b000000000001042d000000000100001900000217000104300000020e002104210000000102000039000000000001042d0000000002000019000000000001042d00000213002104230000000102000039000000000001042d0000000002000019000000000001042d0000021500000432000002160001042e000002170001043000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000f340fa01000000000000000000000000000000000000000000000000000000008da5cb5b00000000000000000000000000000000000000000000000000000000e3a9db1a0000000000000000000000000000000000000000000000000000000051cff8d900000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000008129fc1c000000000000000000000000ffffffffffffffffffffffffffffffffffffffff020000000000000000000000000000000000004000000000000000000000000002000000000000000000000000000000000000200000000000000000000000002da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c44e487b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000008c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000080000000000000000000000000000000000000000000000000000000200000008000000000000000001806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c7265610000000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e07f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024986e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f7420694f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657200000000000000000000000000000000000000640000008000000000000000009cc7f708afc65944829bd487b90b72536b1951864fbfc14e125fc972a6507f390000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff6563697069656e74206d61792068617665207265766572746564000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c20727084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5416464726573733a20696e73756666696369656e742062616c616e63650000000000000000000000000000000000000000000064000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000018b532e372f3b4020a6dd5ffb28e63fce9dab8e991740d8c60724ec4c1e2ce5" + ], + "address": "0x094d790b0732d375502A6Fd2858f50322538dDB3", + "txHash": "0x4f4639c9df8621572c144abee147812f3c4050298e733b5993e9c8c81b245ca6" + }, + { + "constructorArgs": [ + "0x8B24B403ad457c03Ba177b34c081dcB0D07A04b6" + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [ + "0x000200000000000200050000000000020000006003100270000000860330019700010000003103550000008004000039000000400040043f0000000100200190000000270000c13d000000040030008c0000015e0000413d000000000201043b000000e002200270000000880020009c0000002f0000a13d000000890020009c000000530000213d0000008c0020009c000000cb0000613d0000008d0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d000000000010043f0000006501000039000000200010043f0000000001000019021501fa0000040f000000000101041a000000800010043f0000009b01000041000002160001042e0000000001000416000000000001004b0000015e0000c13d0000002001000039000001000010044300000120000004430000008701000041000002160001042e0000008e0020009c000000880000613d0000008f0020009c000000f10000613d000000900020009c0000015e0000c13d0000000001000416000000000001004b0000015e0000c13d0000000003000415000000050330008a0000000503300210000000000200041a0000ff00012001900000010f0000c13d0000000003000415000000040330008a0000000503300210000000ff002001900000010f0000c13d000000a10120019700000101011001bf0000000002000019000000000010041b0000ff0000100190000001320000c13d000000400100043d0000006402100039000000a60300004100000000003204350000004402100039000000a703000041000000000032043500000024021000390000002b030000390000016d0000013d0000008a0020009c000000d40000613d0000008b0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000301041a0000000002000416000000000032001a000001de0000413d0000000003320019000000000031041b000000400100043d0000000000210435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000020300003900000094040000410000000305000029000001590000013d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000201041a000200000002001d000000000001041b000000aa010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800a02000039021502100000040f0000000100200190000001600000613d000000000101043b0000000203000029000000000031004b0000017b0000813d000000400100043d0000004402100039000000b003000041000000000032043500000024021000390000001d03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000b1011001c700000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000101041a0000009101100197000000800010043f0000009b01000041000002160001042e000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009100100198000001780000c13d0000009701000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000009801000041000000c40010043f0000009901000041000000e40010043f0000009a0100004100000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000201041a00000091052001970000000003000411000000000035004b000001060000c13d000000a202200197000000000021041b0000000001000414000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410000000006000019000001590000013d0000009701000041000000800010043f0000002001000039000000840010043f000000a40010043f000000a801000041000000c40010043f000000a9010000410000021700010430000300000003001d000100000001001d000200000002001d0000009c010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800202000039021502100000040f0000000100200190000001600000613d000000000101043b000000000001004b000001610000c13d0000000202000029000000ff0120018f000000010010008c00000003010000290000000501100270000000000100003f000000010100603f000001640000c13d000000010000006b000000430000613d000000b201200197000000010200003900000001011001bf000000000010041b0000ff0000100190000000490000613d000300000002001d000000000100041100000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f00000001002001900000015e0000613d000000030000006b0000015c0000c13d000000000200041a000000b301200197000000000010041b0000000103000039000000400100043d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000a5040000410215020b0000040f00000001002001900000015e0000613d0000000001000019000002160001042e00000000010000190000021700010430000000000001042f00000003010000290000000501100270000000000100003f000000400100043d00000064021000390000009e03000041000000000032043500000044021000390000009f03000041000000000032043500000024021000390000002e03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000a0011001c70000021700010430021501e40000040f0000000001000019000002160001042e00000000010004140000000302000029000000040020008c000001820000c13d00000001020000390000000001000031000001930000013d000000860010009c0000008601008041000000c001100210000000000003004b000001890000c13d00000003020000290000018d0000013d000000a3011001c70000800902000039000000030400002900000000050000190215020b0000040f000000020300002900010000000103550000006001100270000000860010019d0000008601100197000000000001004b000001a60000c13d000000400100043d0000000100200190000001ae0000613d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d020000390000000203000039000000af04000041000000860000013d000000ab0010009c000001b70000413d0000009501000041000000000010043f0000004101000039000000040010043f000000960100004100000217000104300000006402100039000000ad0300004100000000003204350000004402100039000000ae03000041000000000032043500000024021000390000003a030000390000016d0000013d0000001f04100039000000b4044001970000003f04400039000000b405400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000000ac0050009c000001a80000213d0000000100600190000001a80000c13d000000400050043f0000000006140436000000b4091001980000001f0410018f00000000019600190000000105000367000001d00000613d000000000705034f000000007807043c0000000006860436000000000016004b000001cc0000c13d000000000004004b000001950000613d000000000695034f0000000304400210000000000501043300000000054501cf000000000545022f000000000606043b0000010004400089000000000646022f00000000044601cf000000000454019f0000000000410435000001950000013d0000009501000041000000000010043f0000001101000039000000040010043f0000009601000041000002170001043000000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f0000000100200190000001f70000613d000000000001042d00000000010000190000021700010430000000000001042f0000000002000414000000860020009c0000008602008041000000c002200210000000860010009c00000086010080410000004001100210000000000121019f00000092011001c70000801002000039021502100000040f0000000100200190000002090000613d000000000101043b000000000001042d000000000100001900000217000104300000020e002104210000000102000039000000000001042d0000000002000019000000000001042d00000213002104230000000102000039000000000001042d0000000002000019000000000001042d0000021500000432000002160001042e000002170001043000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000f340fa01000000000000000000000000000000000000000000000000000000008da5cb5b00000000000000000000000000000000000000000000000000000000e3a9db1a0000000000000000000000000000000000000000000000000000000051cff8d900000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000008129fc1c000000000000000000000000ffffffffffffffffffffffffffffffffffffffff020000000000000000000000000000000000004000000000000000000000000002000000000000000000000000000000000000200000000000000000000000002da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c44e487b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000008c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000080000000000000000000000000000000000000000000000000000000200000008000000000000000001806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c7265610000000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e07f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024986e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f7420694f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657200000000000000000000000000000000000000640000008000000000000000009cc7f708afc65944829bd487b90b72536b1951864fbfc14e125fc972a6507f390000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff6563697069656e74206d61792068617665207265766572746564000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c20727084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5416464726573733a20696e73756666696369656e742062616c616e63650000000000000000000000000000000000000000000064000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000018b532e372f3b4020a6dd5ffb28e63fce9dab8e991740d8c60724ec4c1e2ce5" + ], + "address": "0x9DEb6b9ac6ff16a56c177fC7DaDdA67bd4cF0751", + "txHash": "0x60e28bcb563d6945a3ffedb3676838f36f62b7e898dd74a70bab5a225f9249d6" + }, + { + "constructorArgs": [ + "0x034F9B8CE83901EB22a1589072D7406f379669F1" + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [ + "0x000200000000000200050000000000020000006003100270000000860330019700010000003103550000008004000039000000400040043f0000000100200190000000270000c13d000000040030008c0000015e0000413d000000000201043b000000e002200270000000880020009c0000002f0000a13d000000890020009c000000530000213d0000008c0020009c000000cb0000613d0000008d0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d000000000010043f0000006501000039000000200010043f0000000001000019021501fa0000040f000000000101041a000000800010043f0000009b01000041000002160001042e0000000001000416000000000001004b0000015e0000c13d0000002001000039000001000010044300000120000004430000008701000041000002160001042e0000008e0020009c000000880000613d0000008f0020009c000000f10000613d000000900020009c0000015e0000c13d0000000001000416000000000001004b0000015e0000c13d0000000003000415000000050330008a0000000503300210000000000200041a0000ff00012001900000010f0000c13d0000000003000415000000040330008a0000000503300210000000ff002001900000010f0000c13d000000a10120019700000101011001bf0000000002000019000000000010041b0000ff0000100190000001320000c13d000000400100043d0000006402100039000000a60300004100000000003204350000004402100039000000a703000041000000000032043500000024021000390000002b030000390000016d0000013d0000008a0020009c000000d40000613d0000008b0020009c0000015e0000c13d000000240030008c0000015e0000413d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000301041a0000000002000416000000000032001a000001de0000413d0000000003320019000000000031041b000000400100043d0000000000210435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000020300003900000094040000410000000305000029000001590000013d000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009101100197000300000001001d000000000010043f0000006501000039000000200010043f0000000001000414000000860010009c0000008601008041000000c00110021000000092011001c70000801002000039021502100000040f00000001002001900000015e0000613d000000000101043b000000000201041a000200000002001d000000000001041b000000aa010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800a02000039021502100000040f0000000100200190000001600000613d000000000101043b0000000203000029000000000031004b0000017b0000813d000000400100043d0000004402100039000000b003000041000000000032043500000024021000390000001d03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000b1011001c700000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000101041a0000009101100197000000800010043f0000009b01000041000002160001042e000000240030008c0000015e0000413d0000000002000416000000000002004b0000015e0000c13d0000000401100370000000000101043b000000910010009c0000015e0000213d0000003302000039000000000202041a00000091022001970000000003000411000000000032004b000001060000c13d0000009100100198000001780000c13d0000009701000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000009801000041000000c40010043f0000009901000041000000e40010043f0000009a0100004100000217000104300000000001000416000000000001004b0000015e0000c13d0000003301000039000000000201041a00000091052001970000000003000411000000000035004b000001060000c13d000000a202200197000000000021041b0000000001000414000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410000000006000019000001590000013d0000009701000041000000800010043f0000002001000039000000840010043f000000a40010043f000000a801000041000000c40010043f000000a9010000410000021700010430000300000003001d000100000001001d000200000002001d0000009c010000410000000000100443000000000100041000000004001004430000000001000414000000860010009c0000008601008041000000c0011002100000009d011001c70000800202000039021502100000040f0000000100200190000001600000613d000000000101043b000000000001004b000001610000c13d0000000202000029000000ff0120018f000000010010008c00000003010000290000000501100270000000000100003f000000010100603f000001640000c13d000000010000006b000000430000613d000000b201200197000000010200003900000001011001bf000000000010041b0000ff0000100190000000490000613d000300000002001d000000000100041100000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f00000001002001900000015e0000613d000000030000006b0000015c0000c13d000000000200041a000000b301200197000000000010041b0000000103000039000000400100043d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d02000039000000a5040000410215020b0000040f00000001002001900000015e0000613d0000000001000019000002160001042e00000000010000190000021700010430000000000001042f00000003010000290000000501100270000000000100003f000000400100043d00000064021000390000009e03000041000000000032043500000044021000390000009f03000041000000000032043500000024021000390000002e03000039000000000032043500000097020000410000000000210435000000040210003900000020030000390000000000320435000000860010009c00000086010080410000004001100210000000a0011001c70000021700010430021501e40000040f0000000001000019000002160001042e00000000010004140000000302000029000000040020008c000001820000c13d00000001020000390000000001000031000001930000013d000000860010009c0000008601008041000000c001100210000000000003004b000001890000c13d00000003020000290000018d0000013d000000a3011001c70000800902000039000000030400002900000000050000190215020b0000040f000000020300002900010000000103550000006001100270000000860010019d0000008601100197000000000001004b000001a60000c13d000000400100043d0000000100200190000001ae0000613d0000000000310435000000860010009c000000860100804100000040011002100000000002000414000000860020009c0000008602008041000000c002200210000000000112019f00000093011001c70000800d020000390000000203000039000000af04000041000000860000013d000000ab0010009c000001b70000413d0000009501000041000000000010043f0000004101000039000000040010043f000000960100004100000217000104300000006402100039000000ad0300004100000000003204350000004402100039000000ae03000041000000000032043500000024021000390000003a030000390000016d0000013d0000001f04100039000000b4044001970000003f04400039000000b405400197000000400400043d0000000005540019000000000045004b00000000060000390000000106004039000000ac0050009c000001a80000213d0000000100600190000001a80000c13d000000400050043f0000000006140436000000b4091001980000001f0410018f00000000019600190000000105000367000001d00000613d000000000705034f000000007807043c0000000006860436000000000016004b000001cc0000c13d000000000004004b000001950000613d000000000695034f0000000304400210000000000501043300000000054501cf000000000545022f000000000606043b0000010004400089000000000646022f00000000044601cf000000000454019f0000000000410435000001950000013d0000009501000041000000000010043f0000001101000039000000040010043f0000009601000041000002170001043000000091061001970000003301000039000000000201041a000000a203200197000000000363019f000000000031041b00000000010004140000009105200197000000860010009c0000008601008041000000c001100210000000a3011001c70000800d020000390000000303000039000000a4040000410215020b0000040f0000000100200190000001f70000613d000000000001042d00000000010000190000021700010430000000000001042f0000000002000414000000860020009c0000008602008041000000c002200210000000860010009c00000086010080410000004001100210000000000121019f00000092011001c70000801002000039021502100000040f0000000100200190000002090000613d000000000101043b000000000001042d000000000100001900000217000104300000020e002104210000000102000039000000000001042d0000000002000019000000000001042d00000213002104230000000102000039000000000001042d0000000002000019000000000001042d0000021500000432000002160001042e000002170001043000000000000000000000000000000000000000000000000000000000ffffffff0000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000f340fa01000000000000000000000000000000000000000000000000000000008da5cb5b00000000000000000000000000000000000000000000000000000000e3a9db1a0000000000000000000000000000000000000000000000000000000051cff8d900000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000008129fc1c000000000000000000000000ffffffffffffffffffffffffffffffffffffffff020000000000000000000000000000000000004000000000000000000000000002000000000000000000000000000000000000200000000000000000000000002da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c44e487b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000008c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008400000080000000000000000000000000000000000000000000000000000000200000008000000000000000001806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000647920696e697469616c697a6564000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e747261637420697320616c7265610000000000000000000000000000000000000084000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000ffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e07f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024986e697469616c697a696e67000000000000000000000000000000000000000000496e697469616c697a61626c653a20636f6e7472616374206973206e6f7420694f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657200000000000000000000000000000000000000640000008000000000000000009cc7f708afc65944829bd487b90b72536b1951864fbfc14e125fc972a6507f390000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff6563697069656e74206d61792068617665207265766572746564000000000000416464726573733a20756e61626c6520746f2073656e642076616c75652c20727084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5416464726573733a20696e73756666696369656e742062616c616e63650000000000000000000000000000000000000000000064000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000018b532e372f3b4020a6dd5ffb28e63fce9dab8e991740d8c60724ec4c1e2ce5" + ], + "address": "0x80215884BB658b72741ad27Dd9C58414De04f01B", + "txHash": "0x35f6badd09beaa02bab9f07276889e04d88abd101be652dc4898b69c37ae6d12" + } + ] +} diff --git a/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/sale/v4/FlatPriceSaleFactory.sol/FlatPriceSaleFactory_v_4_0.json b/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/sale/v4/FlatPriceSaleFactory.sol/FlatPriceSaleFactory_v_4_0.json new file mode 100644 index 00000000..961ee47d --- /dev/null +++ b/zksync-ts/deployments-zk/zkSyncSepoliaTestnet/contracts/sale/v4/FlatPriceSaleFactory.sol/FlatPriceSaleFactory_v_4_0.json @@ -0,0 +1,330 @@ +{ + "sourceName": "contracts/sale/v4/FlatPriceSaleFactory.sol", + "contractName": "FlatPriceSaleFactory_v_4_0", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract FlatPriceSale_v_4_0", + "name": "clone", + "type": "address" + }, + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "indexed": false, + "internalType": "struct Config", + "name": "config", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "string", + "name": "baseCurrency", + "type": "string" + }, + { + "indexed": false, + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "nativeOracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nativeOracleHeartbeat", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "nativePaymentsEnabled", + "type": "bool" + } + ], + "name": "NewSale", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "components": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saleMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userMaximum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchaseMinimum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQueueTime", + "type": "uint256" + }, + { + "internalType": "string", + "name": "URI", + "type": "string" + } + ], + "internalType": "struct Config", + "name": "_config", + "type": "tuple" + }, + { + "internalType": "string", + "name": "_baseCurrency", + "type": "string" + }, + { + "internalType": "bool", + "name": "_nativePaymentsEnabled", + "type": "bool" + }, + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck", + "name": "_nativeTokenPriceOracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nativeTokenPriceOracleHeartbeat", + "type": "uint256" + }, + { + "internalType": "contract IERC20Upgradeable[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "contract IOracleOrL2OracleWithSequencerCheck[]", + "name": "oracles", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "oracleHeartbeats", + "type": "uint256[]" + }, + { + "internalType": "uint8[]", + "name": "decimals", + "type": "uint8[]" + } + ], + "name": "newSale", + "outputs": [ + { + "internalType": "contract FlatPriceSale_v_4_0", + "name": "sale", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "name": "upgradeFutureSales", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x0004000000000002001a0000000000020000006004100270000000f60340019700030000003103550002000000010355000000f60040019d0000000100200190000000b20000c13d0000008002000039000000400020043f000000040030008c000001770000413d000000000201043b000000e002200270000000fe0020009c000000f20000a13d000000ff0020009c0000010a0000213d000001020020009c000001240000613d000001030020009c000001770000c13d000001440030008c000001770000413d0000000002000416000000000002004b000001770000c13d0000000402100370000000000a02043b000000f900a0009c000001770000213d0000002402100370000000000902043b0000010d0090009c000001770000213d00000000029300490000010e0020009c000001770000213d000001240020008c000001770000413d0000004402100370000000000202043b0000010d0020009c000001770000213d0000002304200039000000000034004b000001770000813d0000000408200039000000000481034f000000000404043b0000010d0040009c000001770000213d00000000024200190000002402200039000000000032004b000001770000213d0000006402100370000000000602043b000000000006004b0000000002000039000000010200c039000000000026004b000001770000c13d0000008402100370000000000702043b000000f90070009c000001770000213d000000c402100370000000000202043b0000010d0020009c000001770000213d0000002304200039000000000034004b000001770000813d0000000404200039000000000441034f000000000504043b0000010d0050009c000001770000213d001a00240020003d00000005025002100000001a02200029000000000032004b000001770000213d000000e402100370000000000202043b0000010d0020009c000001770000213d0000002304200039000000000034004b000001770000813d0000000404200039000000000441034f000000000404043b001900000004001d0000010d0040009c000001770000213d001800240020003d000000190200002900000005022002100000001802200029000000000032004b000001770000213d0000010402100370000000000202043b0000010d0020009c000001770000213d0000002304200039000000000034004b000001770000813d0000000404200039000000000441034f000000000404043b001500000004001d0000010d0040009c000001770000213d000000240420003900000015020000290000000502200210001300000002001d001200000004001d0000000002420019000000000032004b000001770000213d0000012402100370000000000202043b0000010d0020009c000001770000213d0000002304200039000000000034004b000001770000813d0000000404200039000000000141034f000000000101043b001400000001001d0000010d0010009c000001770000213d001600240020003d000000140100002900000005011002100000001601100029000000000031004b000001770000213d000d0000000a001d001100000009001d000e00000008001d000f00000007001d001000000006001d001700000005001d0000000101000039000000000101041a0000010f02000041000000a40020043f000000f901100197000001040010043f00000000010004140000011002000041000000800020043f000000840000043f0000006002000039000000c40020043f0000002002000039000000e40020043f000000f60010009c000000f601008041000000c00110021000000111011001c7000080060200003903d403ca0000040f0000000100200190000001790000613d000000000101043b000000000001004b0000019d0000c13d000000030100036700000001020000310000017d0000013d0000000002000416000000000002004b000001770000c13d0000001f02300039000000f7022001970000008002200039000000400020043f0000001f0430018f000000f8053001980000008002500039000000c30000613d0000008006000039000000000701034f000000007807043c0000000006860436000000000026004b000000bf0000c13d000000000004004b000000d00000613d000000000151034f0000000304400210000000000502043300000000054501cf000000000545022f000000000101043b0000010004400089000000000141022f00000000014101cf000000000151019f0000000000120435000000200030008c000001770000413d000000800300043d000000f90030009c000001770000213d000000000100041a000000fa021001970000000006000411000000000262019f000000000020041b0000000002000414000000f905100197000000f60020009c000000f602008041000000c001200210000000fb011001c70000800d02000039001a00000003001d0000000303000039000000fc0400004103d403ca0000040f0000001a030000290000000100200190000001770000613d0000000101000039000000000201041a000000fa02200197000000000232019f000000000021041b000000200100003900000100001004430000012000000443000000fd01000041000003d50001042e000001040020009c0000011e0000613d000001050020009c000001480000613d000001060020009c000001770000c13d000000240030008c000001770000413d0000000002000416000000000002004b000001770000c13d0000000401100370000000000101043b000000f90010009c000001770000213d001a00000001001d03d403b30000040f0000000101000039000000000201041a000000fa022001970000001a022001af000000000021041b0000000001000019000003d50001042e000001000020009c0000012c0000613d000001010020009c000001770000c13d0000000001000416000000000001004b000001770000c13d000000c001000039000000400010043f0000000301000039000000800010043f0000010702000041000000a00020043f0000002003000039000000c00030043f000000e00010043f000001000020043f000001030000043f0000010801000041000003d50001042e0000000001000416000000000001004b000001770000c13d0000000101000039000000000101041a000001280000013d0000000001000416000000000001004b000001770000c13d000000000100041a000000f901100197000000800010043f0000011c01000041000003d50001042e000000240030008c000001770000413d0000000002000416000000000002004b000001770000c13d0000000401100370000000000601043b000000f90060009c000001770000213d000000000100041a000000f9021001970000000005000411000000000052004b000001600000c13d000000000006004b000001690000c13d0000010901000041000000800010043f0000002001000039000000840010043f0000002601000039000000a40010043f0000010a01000041000000c40010043f0000010b01000041000000e40010043f0000010c01000041000003d6000104300000000001000416000000000001004b000001770000c13d000000000100041a000000f9021001970000000005000411000000000052004b000001600000c13d000000fa01100197000000000010041b0000000001000414000000f60010009c000000f601008041000000c001100210000000fb011001c70000800d020000390000000303000039000000fc04000041000000000600001903d403ca0000040f0000000100200190000001770000613d0000000001000019000003d50001042e0000010901000041000000800010043f0000002001000039000000840010043f000000a40010043f0000011d01000041000000c40010043f0000011e01000041000003d600010430000000fa01100197000000000161019f000000000010041b0000000001000414000000f60010009c000000f601008041000000c001100210000000fb011001c70000800d020000390000000303000039000000fc0400004103d403ca0000040f00000001002001900000015e0000c13d0000000001000019000003d60001043000030000000103550000006002100270000100f60020019d000000f6022001970000011f052001980000001f0620018f000000400300043d0000000004530019000001880000613d000000000701034f0000000008030019000000007907043c0000000008980436000000000048004b000001840000c13d000000000006004b000001950000613d000000000151034f0000000305600210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f0000000000140435000000f60020009c000000f6020080410000006001200210000000f60030009c000000f6030080410000004002300210000000000112019f000003d6000104300000000102000039000000000402041a000000400200043d000000a00300003900000000053204360000001108000029000c00040080003d00000002030003670000000c06300360000000000606043b000000f90060009c000001770000213d000000a00720003900000000006704350000000c09000029000b00200090003d0000000b06300360000000000606043b000000c0072000390000000000670435000a00400090003d0000000a06300360000000000606043b000000e0072000390000000000670435000900600090003d0000000906300360000000000606043b00000100072000390000000000670435000800800090003d0000000806300360000000000606043b00000120072000390000000000670435000700a00090003d0000000706300360000000000606043b00000140072000390000000000670435000600c00090003d0000000606300360000000000606043b000001600720003900000000006704350000018006200039000500e00090003d0000000507300360000000000707043b0000000000760435000401000090003d000000040630036000000000070000310000000008870049000000230880008a000000000606043b0000011209600197000001120a800197000000000ba9013f0000000000a9004b00000000090000190000011209004041000000000086004b000000000800001900000112080080410000011200b0009c000000000908c019000000000009004b000001770000c13d0000000c08600029000000000683034f000000000606043b0000010d0060009c000001770000213d00000020088000390000000007670049000000000078004b000000000900001900000112090020410000011207700197000001120a800197000000000b7a013f00000000007a004b000000000700001900000112070040410000011200b0009c000000000709c019000000000007004b000001770000c13d000001a00720003900000120090000390000000000970435000001c0072000390000000000670435000000000983034f0000011f0a6001980000001f0b60018f000001e0072000390000000008a70019000002070000613d000000000c09034f000000000d07001900000000ce0c043c000000000ded043600000000008d004b000002030000c13d00000000000b004b000002140000613d0000000009a9034f000000030ab00210000000000b080433000000000bab01cf000000000bab022f000000000909043b000001000aa000890000000009a9022f0000000009a901cf0000000009b9019f0000000000980435000000000876001900000000000804350000001f066000390000011f066001970000000006760019000000000726004900000000007504350000000e07000029000000000573034f000200200070003d0000000209300360000000000705043b00000000067604360000011f0a7001980000001f0b70018f0000000008a600190000022b0000613d000000000509034f000000000c060019000000005d05043c000000000cdc043600000000008c004b000002270000c13d000000f90540019700000000000b004b000002390000613d0000000004a9034f0000000309b00210000000000a080433000000000a9a01cf000000000a9a022f000000000404043b0000010009900089000000000494022f00000000049401cf0000000004a4019f00000000004804350000000004670019000000000004043500000040042000390000000f080000290000000000840435000000a403300370000000000303043b000000800420003900000010080000290000000000840435000000600420003900000000003404350000001f037000390000011f0330019700000000042600490000000003340019000000f60030009c000000f6030080410000006003300210000000f60020009c000000f6020080410000004002200210000000000223019f0000000003000414000000f60030009c000000f603008041000000c003300210000000000223019f000000f906100197000000fb012001c70000800d0200003900000003030000390000011304000041000300000006001d03d403ca0000040f0000000100200190000001770000613d00000114010000410000000000100443000000030100002900000004001004430000000001000414000000f60010009c000000f601008041000000c00110021000000115011001c7000080020200003903d403cf0000040f00000001002001900000038a0000613d000000000101043b000000000001004b00000011040000290000000d03000029000001770000613d000000400500043d00000024015000390000014002000039000000000021043500000116010000410000000000150435000100000005001d0000000401500039000000000031043500000002010003670000000c02100360000000000202043b000000f90020009c000001770000213d0000000105000029000001440350003900000000002304350000000b02100360000000000202043b000001640350003900000000002304350000000a02100360000000000202043b000001840350003900000000002304350000000902100360000000000202043b000001a40350003900000000002304350000000802100360000000000202043b000001c40350003900000000002304350000000702100360000000000202043b000001e40350003900000000002304350000000602100360000000000202043b0000020403500039000000000023043500000005021003600000022403500039000000000202043b0000000000230435000000040210036000000000030000310000000004430049000000230440008a000000000202043b00000112052001970000011206400197000000000765013f000000000065004b00000000050000190000011205004041000000000042004b00000000040000190000011204008041000001120070009c000000000504c019000000000005004b000001770000c13d0000000c04200029000000000241034f000000000202043b0000010d0020009c000001770000213d00000020044000390000000003230049000000000034004b0000000005000019000001120500204100000112033001970000011206400197000000000736013f000000000036004b00000000030000190000011203004041000001120070009c000000000305c019000000000003004b000001770000c13d000000010800002900000244038000390000012005000039000000000053043500000264038000390000000000230435000000000541034f0000011f062001980000001f0720018f00000284038000390000000004630019000002d50000613d000000000805034f0000000009030019000000008a08043c0000000009a90436000000000049004b000002d10000c13d000000000007004b000002e20000613d000000000565034f0000000306700210000000000704043300000000076701cf000000000767022f000000000505043b0000010006600089000000000565022f00000000056501cf000000000575019f0000000000540435000000000432001900000000000404350000001f022000390000011f022001970000000104000029000000440440003900000280052000390000000000540435000000000232001900000002051003600000000e03100360000000000303043b00000000023204360000011f063001980000001f0730018f0000000004620019000002f90000613d000000000805034f0000000009020019000000008a08043c0000000009a90436000000000049004b000002f50000c13d000000000007004b000003060000613d000000000565034f0000000306700210000000000704043300000000076701cf000000000767022f000000000505043b0000010006600089000000000565022f00000000056501cf000000000575019f000000000054043500000000042300190000000000040435000000010600002900000084046000390000000f050000290000000000540435000000640460003900000010050000290000000000540435000000a404100370000000000404043b000000a40560003900000000004504350000001f033000390000011f0330019700000000022300190000000003620049000000040330008a000000c404600039000000000034043500000017030000290000000002320436000000000003004b0000032a0000613d00000000030000190000001a04100360000000000404043b000000f90040009c0000001705000029000001770000213d00000000024204360000001a04000029001a00200040003d0000000103300039000000000053004b0000031f0000413d00000001040000290000000003420049000000040330008a000000e404400039000000000034043500000019030000290000000002320436000000000003004b0000033e0000613d00000000030000190000001804100360000000000404043b000000f90040009c000001770000213d00000000024204360000001804000029001800200040003d0000000103300039000000190030006c000003340000413d00000001040000290000000003420049000000040330008a0000010404400039000000000034043500000015040000290000000003420436000001170040009c000001770000213d00000013050000290000001f0450018f000000000005004b000003510000613d00000012051003600000001306300029000000005705043c0000000003730436000000000063004b0000034d0000c13d000000000004004b0000001302200029000000010400002900000000034200490000001c03300039000001240440003900000000003404350000002003200039000000140400002900000000004304350000004002200039000000000004004b000003690000613d00000000030000190000001604100360000000000404043b000000ff0040008c000001770000213d00000000024204360000001604000029001600200040003d0000000103300039000000140030006c0000035f0000413d00000000010004140000000303000029000000040030008c000003810000613d00000001030000290000000002320049000000f60020009c000000f6020080410000006002200210000000f60030009c000000f6030080410000004003300210000000000232019f000000f60010009c000000f601008041000000c001100210000000000112019f000000030200002903d403ca0000040f0000006003100270000100f60030019d00030000000103550000000100200190000003940000613d0000000101000029000001180010009c0000038b0000413d0000011a01000041000000000010043f0000004101000039000000040010043f0000011b01000041000003d600010430000000000001042f0000000102000029000000400020043f00000003010000290000000000120435000000f60020009c000000f602008041000000400120021000000119011001c7000003d50001042e000000f6033001970000001f0530018f000000f806300198000000400200043d0000000004620019000003a00000613d000000000701034f0000000008020019000000007907043c0000000008980436000000000048004b0000039c0000c13d000000000005004b000003ad0000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000006001300210000000f60020009c000000f6020080410000004002200210000000000112019f000003d600010430000000000100041a000000f9011001970000000002000411000000000021004b000003b90000c13d000000000001042d000000400100043d00000044021000390000011d0300004100000000003204350000010902000041000000000021043500000024021000390000002003000039000000000032043500000004021000390000000000320435000000f60010009c000000f601008041000000400110021000000120011001c7000003d600010430000000000001042f000003cd002104210000000102000039000000000001042d0000000002000019000000000001042d000003d2002104230000000102000039000000000001042d0000000002000019000000000001042d000003d400000432000003d50001042e000003d600010430000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000001ffffffe000000000000000000000000000000000000000000000000000000000ffffffe0000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e00000000200000000000000000000000000000040000001000000000000000000000000000000000000000000000000000000000000000000000000008da5cb5a00000000000000000000000000000000000000000000000000000000f2fde38a00000000000000000000000000000000000000000000000000000000f2fde38b00000000000000000000000000000000000000000000000000000000ffa1ad74000000000000000000000000000000000000000000000000000000008da5cb5b00000000000000000000000000000000000000000000000000000000a7443405000000000000000000000000000000000000000000000000000000005c60da1b00000000000000000000000000000000000000000000000000000000715018a6000000000000000000000000000000000000000000000000000000007bfce4df342e3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000c0000000000000000008c379a0000000000000000000000000000000000000000000000000000000004f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084000000800000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0100003900d100de6d0a86ddea4a8d758b1fb6c3ce5354f69ef1ee10db885d439c4d535bdea7cd8a978f128b93471df48c7dbab89d703809115bdc118c235bfd02000000000000000000000000000000000000a4000000800000000000000000800000000000000000000000000000000000000000000000000000000000000062480a5b22a42a66c986adaff1aa45bcd50564da47d78e0a13351b7f6f6d6c491806aa1896bbf26568e884a7374b41e002500962caba6a15023a8d90e8508b830200000200000000000000000000000000000024000000000000000000000000be8423de0000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000200000000000000000000000004e487b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000200000008000000000000000004f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65720000000000000000000000000000000000000064000000800000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ed0ed8a75f891e5245fa619dd2557c1c8875a5596e0ab44b55a11c837a0d8f65", + "entries": [ + { + "constructorArgs": [ + "0x80215884BB658b72741ad27Dd9C58414De04f01B" + ], + "salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "deploymentType": "create", + "factoryDeps": [ + "0x0002000000000002000100000000000200000060031002700000002e08300197000100000081035500000001002001900000004b0000c13d0000001f0380018f00000030048001980000008002400039000000110000613d0000008005000039000000000601034f000000006706043c0000000005750436000000000025004b0000000d0000c13d000100000008001d000000000003004b0000001f0000613d000000000141034f0000000303300210000000000402043300000000043401cf000000000434022f000000000101043b0000010003300089000000000131022f00000000013101cf000000000141019f00000000001204350000003301000041000000000010044300000000010004120000000400100443000000240000044300000000010004140000002e0010009c0000002e01008041000000c00110021000000034011001c7000080050200003900b200a80000040f0000000100200190000000700000613d000000000201043b0000000001000414000000040020008c000000710000c13d0000000103000367000000000100003100000036041001980000001f0510018f00000080024000390000003d0000613d0000008006000039000000000703034f000000007807043c0000000006860436000000000026004b000000390000c13d000000000005004b000000960000613d000000000343034f0000000304500210000000000502043300000000054501cf000000000545022f000000000303043b0000010004400089000000000343022f00000000034301cf000000000353019f0000000000320435000000960000013d0000000002000416000000000002004b0000006e0000c13d0000001f028000390000002f02200197000000a002200039000000400020043f0000001f0380018f0000003004800198000000a0024000390000005c0000613d000000a005000039000000000601034f000000006706043c0000000005750436000000000025004b000000580000c13d000000000003004b000000690000613d000000000141034f0000000303300210000000000402043300000000043401cf000000000434022f000000000101043b0000010003300089000000000131022f00000000013101cf000000000141019f0000000000120435000000200080008c0000006e0000413d000000a00100043d000000310010009c0000009b0000a13d0000000001000019000000b400010430000000000001042f000000010300002900000060033002100000002e0010009c0000002e01008041000000c001100210000000000131019f00000035011001c700b200ad0000040f000100000001035500000060031002700000001f0530018f0000002e0030019d00000030063001980000008004600039000000860000613d0000008007000039000000000801034f000000008908043c0000000007970436000000000047004b000000820000c13d000000000005004b000000930000613d000000000161034f0000000305500210000000000604043300000000065601cf000000000656022f000000000101043b0000010005500089000000000151022f00000000015101cf000000000161019f00000000001404350000002e013001970000000100200190000000a40000613d0000002e0010009c0000002e01008041000000600110021000000035011001c7000000b30001042e000000800010043f0000014000000443000001600010044300000020010000390000010000100443000000010100003900000120001004430000003201000041000000b30001042e000000600110021000000035011001c7000000b400010430000000000001042f000000ab002104230000000102000039000000000001042d0000000002000019000000000001042d000000b0002104250000000102000039000000000001042d0000000002000019000000000001042d000000b200000432000000b30001042e000000b40001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000001ffffffe000000000000000000000000000000000000000000000000000000000ffffffe0000000000000000000000000ffffffffffffffffffffffffffffffffffffffff0000000200000000000000000000000000000080000001000000000000000000310ab089e4439a4c15d089f94afb7896ff553aecb10793d0ab882de59d99a32e02000002000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000000000000800000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000f21e537fa1fe5f0bf8064aafff9a6eb5249e22e6f3d163f71bf692d71d9f7c2c" + ], + "address": "0x8465C8ca6d1b8EDF7444d5E4BB9b45e1F35b1D02", + "txHash": "0x1326f6b3a5354b134456f402c3fe21dfba139b02ed49e1a93e380d042ced504f" + } + ] +} diff --git a/zksync-ts/hardhat.config.ts b/zksync-ts/hardhat.config.ts new file mode 100644 index 00000000..19b90480 --- /dev/null +++ b/zksync-ts/hardhat.config.ts @@ -0,0 +1,54 @@ +import { HardhatUserConfig } from "hardhat/config"; + +import "@matterlabs/hardhat-zksync"; + +const config: HardhatUserConfig = { + defaultNetwork: "zkSyncSepoliaTestnet", + networks: { + zkSyncSepoliaTestnet: { + url: "https://sepolia.era.zksync.dev", + ethNetwork: "sepolia", + zksync: true, + verifyURL: "https://explorer.sepolia.era.zksync.dev/contract_verification" + }, + zkSyncMainnet: { + url: "https://mainnet.era.zksync.io", + ethNetwork: "mainnet", + zksync: true, + verifyURL: + "https://zksync2-mainnet-explorer.zksync.io/contract_verification" + }, + dockerizedNode: { + url: "http://localhost:3050", + ethNetwork: "http://localhost:8545", + zksync: true + }, + inMemoryNode: { + url: "http://127.0.0.1:8011", + ethNetwork: "localhost", // in-memory node doesn't support eth node; removing this line will cause an error + zksync: true + }, + hardhat: { + zksync: true + } + }, + zksolc: { + version: "latest", + settings: { + // find all available options in the official documentation + // https://docs.zksync.io/build/tooling/hardhat/hardhat-zksync-solc#configuration + } + }, + solidity: { + version: "0.8.21", + settings: { + optimizer: { + enabled: true, + runs: 200 + }, + viaIR: true + } + } +}; + +export default config; diff --git a/zksync-ts/package.json b/zksync-ts/package.json new file mode 100644 index 00000000..65ded272 --- /dev/null +++ b/zksync-ts/package.json @@ -0,0 +1,37 @@ +{ + "name": "zksync-hardhat", + "description": "ZKsync smart contracts development with Hardhat", + "private": true, + "author": "Matter Labs", + "license": "MIT", + "repository": "https://github.com/matter-labs/zksync-hardhat-template.git", + "scripts": { + "deploy": "hardhat deploy-zksync --script deploy.ts", + "interact": "hardhat deploy-zksync --script interact.ts", + "compile": "hardhat compile", + "clean": "hardhat clean", + "test": "hardhat test --network hardhat" + }, + "devDependencies": { + "@matterlabs/hardhat-zksync": "^1.1.0", + "@matterlabs/zksync-contracts": "^0.6.1", + "@openzeppelin/contracts": "^4.9.2", + "@nomicfoundation/hardhat-verify": "^2.0.9", + "@types/chai": "^4.3.16", + "@types/mocha": "^10.0.7", + "chai": "^4.5.0", + "dotenv": "^16.4.5", + "ethers": "^6.13.2", + "hardhat": "^2.22.7", + "mocha": "^10.7.0", + "ts-node": "^10.9.2", + "typescript": "^5.5.4", + "zksync-ethers": "^6.11.0" + }, + "dependencies": { + "@chainlink/contracts": "^0.6.1", + "@openzeppelin/contracts": "^4.8.1", + "@openzeppelin/contracts-upgradeable": "^4.9.6", + "hardhat-jest": "^1.0.8" + } +} diff --git a/zksync-ts/test/erc20/myerc20token.test.ts b/zksync-ts/test/erc20/myerc20token.test.ts new file mode 100644 index 00000000..e92eebdd --- /dev/null +++ b/zksync-ts/test/erc20/myerc20token.test.ts @@ -0,0 +1,50 @@ +import { expect } from 'chai'; +import { Contract, Wallet } from "zksync-ethers"; +import { getWallet, deployContract, LOCAL_RICH_WALLETS } from '../../deploy/utils'; +import * as ethers from "ethers"; + +describe("MyERC20Token", function () { + let tokenContract: Contract; + let ownerWallet: Wallet; + let userWallet: Wallet; + + before(async function () { + ownerWallet = getWallet(LOCAL_RICH_WALLETS[0].privateKey); + userWallet = getWallet(LOCAL_RICH_WALLETS[1].privateKey); + + tokenContract = await deployContract("MyERC20Token", [], { wallet: ownerWallet, silent: true }); + }); + + it("Should have correct initial supply", async function () { + const initialSupply = await tokenContract.totalSupply(); + expect(initialSupply).to.equal(BigInt("1000000000000000000000000")); // 1 million tokens with 18 decimals + }); + + it("Should allow owner to burn tokens", async function () { + const burnAmount = ethers.parseEther("10"); // Burn 10 tokens + const tx = await tokenContract.burn(burnAmount); + await tx.wait(); + const afterBurnSupply = await tokenContract.totalSupply(); + expect(afterBurnSupply).to.equal(BigInt("999990000000000000000000")); // 999,990 tokens remaining + }); + + it("Should allow user to transfer tokens", async function () { + const transferAmount = ethers.parseEther("50"); // Transfer 50 tokens + const tx = await tokenContract.transfer(userWallet.address, transferAmount); + await tx.wait(); + const userBalance = await tokenContract.balanceOf(userWallet.address); + expect(userBalance).to.equal(transferAmount); + }); + + it("Should fail when user tries to burn more tokens than they have", async function () { + const userTokenContract = new Contract(await tokenContract.getAddress(), tokenContract.interface, userWallet); + const burnAmount = ethers.parseEther("100"); // Try to burn 100 tokens + try { + await userTokenContract.burn(burnAmount); + expect.fail("Expected burn to revert, but it didn't"); + } catch (error) { + expect(error.message).to.include("burn amount exceeds balance"); + } + }); +}); + diff --git a/zksync-ts/test/nft/mynft.test.ts b/zksync-ts/test/nft/mynft.test.ts new file mode 100644 index 00000000..59ac3583 --- /dev/null +++ b/zksync-ts/test/nft/mynft.test.ts @@ -0,0 +1,52 @@ +import { expect } from 'chai'; +import { Contract, Wallet } from "zksync-ethers"; +import { getWallet, deployContract, LOCAL_RICH_WALLETS } from '../../deploy/utils'; + +describe("MyNFT", function () { + let nftContract: Contract; + let ownerWallet: Wallet; + let recipientWallet: Wallet; + + before(async function () { + ownerWallet = getWallet(LOCAL_RICH_WALLETS[0].privateKey); + recipientWallet = getWallet(LOCAL_RICH_WALLETS[1].privateKey); + + nftContract = await deployContract( + "MyNFT", + ["MyNFTName", "MNFT", "https://mybaseuri.com/token/"], + { wallet: ownerWallet, silent: true } + ); + }); + + it("Should mint a new NFT to the recipient", async function () { + const tx = await nftContract.mint(recipientWallet.address); + await tx.wait(); + const balance = await nftContract.balanceOf(recipientWallet.address); + expect(balance).to.equal(BigInt("1")); + }); + + it("Should have correct token URI after minting", async function () { + const tokenId = 1; // Assuming the first token minted has ID 1 + const tokenURI = await nftContract.tokenURI(tokenId); + expect(tokenURI).to.equal("https://mybaseuri.com/token/1"); + }); + + it("Should allow owner to mint multiple NFTs", async function () { + const tx1 = await nftContract.mint(recipientWallet.address); + await tx1.wait(); + const tx2 = await nftContract.mint(recipientWallet.address); + await tx2.wait(); + const balance = await nftContract.balanceOf(recipientWallet.address); + expect(balance).to.equal(BigInt("3")); // 1 initial nft + 2 minted + }); + + it("Should not allow non-owner to mint NFTs", async function () { + try { + const tx3 = await (nftContract.connect(recipientWallet) as Contract).mint(recipientWallet.address); + await tx3.wait(); + expect.fail("Expected mint to revert, but it didn't"); + } catch (error) { + expect(error.message).to.include("Ownable: caller is not the owner"); + } + }); +}); diff --git a/zksync-ts/tsconfig.json b/zksync-ts/tsconfig.json new file mode 100644 index 00000000..2bc60fdb --- /dev/null +++ b/zksync-ts/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "resolveJsonModule": true + }, + "include": [ + "./hardhat.config.ts", + "./scripts", + "./deploy", + "./test", + "typechain/**/*" + ] +}