diff --git a/Contracts/MockUSDC.sol b/Contracts/MockUSDC.sol new file mode 100644 index 0000000..3de6204 --- /dev/null +++ b/Contracts/MockUSDC.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract MockUSDC is ERC20 { + using SafeERC20 for IERC20; + + constructor() ERC20("Mock USDC", "USDC") { + //1000000 USDC minted + _mint(msg.sender, 1000000 * (10 ** uint256(decimals()))); + } + + function decimals() public pure override returns (uint8) { + return 6; + } + + function mint(address receiver) public { + _mint(receiver, 10000 * (10 ** uint256(decimals()))); + } + + receive() external payable {} + + fallback() external payable {} +} diff --git a/Contracts/Positions.sol b/Contracts/Positions.sol index 4a12791..1213487 100644 --- a/Contracts/Positions.sol +++ b/Contracts/Positions.sol @@ -2,6 +2,16 @@ pragma solidity ^0.8.0; +/// @title POP-Protocol-v1 Position/Asset Contract. +/// @author Anuj Tanwar aka br0wnD3v + +/// @notice Trading contract deploys this contract each time a new asset is to be traded/listed. +/// Is an ERC721 contract. +/// Mint/Burn functions enable a user to hold a Perpetual position or burn the position. + +/// @notice In Testing Phase. +/// @notice Not Audited. + import "./interfaces/IPositions.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; @@ -20,7 +30,6 @@ struct PositionToken { uint256[] position; uint256[] multiplicator; address owner; - uint256 fee; uint256 size; uint256 strikeUpper; uint256 strikeLower; @@ -35,13 +44,9 @@ contract POP_Positions is Ownable, ERC721, IPositions { address public immutable POP_TRADING_CONTRACT; bytes32 public immutable PARENT_PRODUCT; - mapping(uint256 => PositionToken) private idToPosition; + uint256 public immutable SELF_LIMIT; - event PositionStatus( - address indexed owner, - uint256 indexed positionId, - bool indexed isOpen - ); + mapping(uint256 => PositionToken) private idToPosition; modifier onlyPOP_Trading() { if (_msgSender() != POP_TRADING_CONTRACT) @@ -60,12 +65,14 @@ contract POP_Positions is Ownable, ERC721, IPositions { constructor( bytes32 _parentProduct, string memory _productName, - string memory _productSymbol + string memory _productSymbol, + uint256 _intervals ) ERC721(_productName, _productSymbol) { nextTokenId.increment(); PARENT_PRODUCT = _parentProduct; POP_TRADING_CONTRACT = _msgSender(); + SELF_LIMIT = _intervals; } function getNextId() external view returns (uint256) { @@ -108,8 +115,6 @@ contract POP_Positions is Ownable, ERC721, IPositions { nextTokenId.increment(); - emit PositionStatus(_owner, positionId, true); - return positionId; } @@ -120,8 +125,6 @@ contract POP_Positions is Ownable, ERC721, IPositions { currentPosition.isOpen = false; _burn(_positionId); - - emit PositionStatus(currentPosition.owner, _positionId, false); } function update( diff --git a/Contracts/SequencerArray.sol b/Contracts/SequencerArray.sol new file mode 100644 index 0000000..7217866 --- /dev/null +++ b/Contracts/SequencerArray.sol @@ -0,0 +1,51 @@ +//SPDX-License-Identifier:MIT + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract SequencerArray is Ownable { + bytes32 private primaryServiceHash; + uint256 private primaryUpdatedTimestamp; + bytes32 private secondaryServiceHash; + uint256 private secondaryUpdatedTimestamp; + + event updatedPrimary( + bytes32 indexed prev, + bytes32 indexed updated, + uint256 indexed timestamp + ); + event updatedSecondary( + bytes32 indexed prev, + bytes32 indexed updated, + uint256 indexed timestamp + ); + + constructor() {} + + function getLatest() external view returns (bytes32) { + if ( + primaryUpdatedTimestamp > secondaryUpdatedTimestamp && + primaryServiceHash != bytes32(0) + ) return primaryServiceHash; + else return secondaryServiceHash; + } + + function updatePrimary(bytes32 _updated) external onlyOwner { + bytes32 old = primaryServiceHash; + primaryServiceHash = _updated; + primaryUpdatedTimestamp = block.timestamp; + emit updatedPrimary(old, primaryServiceHash, primaryUpdatedTimestamp); + } + + function updateSecondary(bytes32 _updated) external onlyOwner { + bytes32 old = secondaryServiceHash; + secondaryServiceHash = _updated; + secondaryUpdatedTimestamp = block.timestamp; + emit updatedSecondary( + old, + secondaryServiceHash, + secondaryUpdatedTimestamp + ); + } +} diff --git a/Contracts/Trading.sol b/Contracts/Trading.sol index beebaeb..72af03c 100644 --- a/Contracts/Trading.sol +++ b/Contracts/Trading.sol @@ -2,9 +2,22 @@ pragma solidity ^0.8.7; +/// @title POP-Protocol-v1 Trading Contract. +/// @author Anuj Tanwar aka br0wnD3v + +/// @notice Root contract to enable the users to : +/// Initiate the 'Minting' of Perpetual positions. +/// Initiate the 'Burning' of Perpetual positions if exists. +/// @notice Operations shifted to the Sequencer : +/// Minting of the position requested by the user. +/// Burning of the position requested by the user. + +/// @notice In Testing Phase. +/// @notice Not Audited. + import "./Positions.sol"; -import "./interfaces/IStaking.sol"; import "./interfaces/IERC20.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; /// @notice All the relevant error codes. error ERR_POP_Trading_InsufficientApprovedAmount(); @@ -15,6 +28,8 @@ error ERR_POP_Trading_TokenTransferFailed(); error ERR_POP_Trading_UserMintRequestIsInvalid(); error ERR_POP_Trading_UserBurnRequestIsInvalid(); error ERR_POP_Trading_NotThePositionOwner(); +error ERR_POP_Trading_NotAnOperator(); +error ERR_POP_Trading_InvalidIntervalSupplyRatio(); /// @notice Structure that represents a Minting Request initiated by the user and holds relevant info. /// @param receiver The address that initiated the request. @@ -46,8 +61,8 @@ struct BurnRequest { address burner; bytes32 productId; uint256 positionId; - uint256 toReturnFee; uint256 totalFee; + uint256 toReturnFee; bool isFullFilled; } @@ -55,25 +70,28 @@ struct BurnRequest { /// Created by the exchange. BPS:"basis points". /// @param supplyBase The variable q mentioned in the specifications which represent the count of tokens over all the intervals n. /// @param multiplicatorBase Created to help in the maths involved that affects the tokens at each position based on certain rules. -/// @param limit The current upper bound of valid values in the supplyBase and multiplicatorBase ie the limit of say Qn. -/// @param supply Total supply of the given product. Sum of Q1, Q2 .... Qn +/// @param minPrice The lower bound of the price range. +/// @param maxPrice The upper bound of the price range. +/// @param intervals The current upper bound of valid values in the supplyBase and multiplicatorBase ie the limit of say Qn. +/// @param totalSupply Total supply of the given product. Sum of supplyBase. /// @param margin Collateral that a trader must deposit with their broker or exchange in order to open and maintain a leveraged trading position. -/// @param fee Platform fee. +/// @param fee The fee for a given product. Is different for each product. +/// @param positionContract The contract address that represents a given product. Is an ERC721. struct Product { uint256[] supplyBase; uint256[] multiplicatorBase; - uint256 limit; - uint256 supply; - uint256 margin; + uint256 minPrice; + uint256 maxPrice; + uint256 intervals; + uint256 totalSupply; + uint256 margin; // We don't need this since we have the cuts calculated in the yellow paper uint256 fee; // In sbps (10^6). 0.5% = 5000. 0.025% = 250 address positionContract; } -contract Trading is Ownable { +contract POP_Trading is Ownable, ReentrancyGuard { /// VARIABLES ========================================================== - // using ECDSA for bytes32; - using Address for address payable; using Counters for Counters.Counter; /// @notice To the get the next mint/burn request id which hasn't been used before. @@ -85,20 +103,21 @@ contract Trading is Ownable { /// @notice All the associated contracts the Trading contract interacts with in some form. address public sequencerAddress; - address public stakingAddress; - address public vaultAddress; + address public vaultStakingAddress; + /// @notice USDC preferrably. IERC20 public paymentToken; /// @notice Product Id -> Unique Product. Product being the asset being sold on the platform. mapping(bytes32 => Product) public products; + /// @notice Product Id -> Position Contract Address. Each new product when created also deploys an ERC721 too. - /// @notice Having a single contract for every position would create chaos since its will be harder to manage down the line - /// @notice and really hard to scale. + /// @notice Having a single contract for every position would create chaos since its will be harder to + /// manage down the line and really hard to scale. mapping(bytes32 => address) public productToPositionContract; - /// @notice To track incoming mint/burn requests. Initiated by the user. Uses the nextMintRequestId/nextBurnRequestId - /// @notice to map each new id with a respective struct. + /// @notice To track incoming mint/burn requests. Initiated by the user. Uses the + /// nextMintRequestId/nextBurnRequestId to map each new id with a respective struct. mapping(uint256 => MintRequest) public mintRequestIdToStructure; mapping(uint256 => BurnRequest) public burnRequestIdToStructure; @@ -119,15 +138,44 @@ contract Trading is Ownable { /// @param requestId The associated id for each new burn request. event BurnRequested(address indexed user, uint256 indexed requestId); + /// @notice Used to track all the created products. Can be filtered at the front by the limit variable. + /// @param id A unique id alloted to them. + /// @param name The name given to the product. + /// @param symbol The symbol given to the product. + /// @param product The struct that defines the product. event ProductAdded( bytes32 indexed id, - string indexed name, - string indexed symbol, + bytes32 indexed name, + bytes32 indexed symbol, Product product ); + /// @notice Used to track the current activity for a given user. + /// @param owner The trader in question. + /// @param productId The unique identifier for the product. + /// @param positionId The unique identifier for the position opened at the product's ERC721. + /// @param status The latest status for a PPP. The valid values are : + /// Mint Queue - 1 + /// Open - 2 + /// Burn Queue - 3 + /// Burned - 4 + + event PositionStatus( + address indexed owner, + bytes32 indexed productId, + uint256 indexed positionId, + uint256 mintRequestId, + uint256 burnRequestId, + uint256 status + ); /// MODIFIERS =========================================================== + modifier isOperator() { + if (_msgSender() != sequencerAddress && _msgSender() != owner()) + revert ERR_POP_Trading_NotAnOperator(); + _; + } + /// @dev Functions only to be called by the sequencerAddress. modifier onlySequencer() { if (_msgSender() != sequencerAddress) @@ -137,14 +185,14 @@ contract Trading is Ownable { /// @dev Check if the provided productId is valid or not. modifier validProduct(bytes32 _productId) { - if (products[_productId].limit == 0) + if (products[_productId].intervals == 0) revert ERR_POP_Trading_InvalidProductId(); _; } /// @dev Check if the given position even exists for a given prodcut. modifier validPosition(bytes32 _productId, uint256 _positionId) { - if (products[_productId].limit == 0) + if (products[_productId].intervals == 0) revert ERR_POP_Trading_InvalidProductId(); IPositions positionContract = IPositions( @@ -156,30 +204,23 @@ contract Trading is Ownable { _; } - /// CONTRACT STARTS ===================================================== + /// CONTRACT STARTS =================================================== + constructor( address _paymentToken, address _sequencer, - address _staking, - address _vault + address _vaultStaking ) { paymentToken = IERC20(_paymentToken); sequencerAddress = _sequencer; - stakingAddress = _staking; - vaultAddress = _vault; + vaultStakingAddress = _vaultStaking; nextMintRequestId.increment(); nextBurnRequestId.increment(); } - /// @notice FUNCTIONS ================================================= - - /// @dev These should be allowed to be updated but not sure. - function updateSupplyBase() internal {} - - function updateMultiplicatorBase() internal {} - /// =================================================================== + /// @dev The mint function is a 2-Step procedure and burn is a 2-Step procedure too. /// @notice Step 1 being the user sending a request to mint the position which requires them approving a fixed @@ -196,12 +237,18 @@ contract Trading is Ownable { uint256 _size, uint256 _strikeLower, uint256 _strikeUpper - ) external validProduct(_productId) { + ) external validProduct(_productId) nonReentrant returns (uint256) { uint256 fee = products[_productId].fee; + require( + _strikeLower < products[_productId].intervals && + _strikeUpper < products[_productId].intervals, + "Strike price out of bounds." + ); + /// @dev Need to use something with a decimal value since we cant directly use 1 as decimal values are discarded. uint256 protocolCut = _size * fee; - uint256 vaultCut = _size * (1 - fee); + uint256 vaultCut = _size * (10 ** 6 - fee); if ( paymentToken.allowance(_msgSender(), address(this)) < @@ -213,7 +260,7 @@ contract Trading is Ownable { protocolCut + vaultCut ); if (!success) revert ERR_POP_Trading_TokenTransferFailed(); - success = paymentToken.transfer(vaultAddress, vaultCut); + success = paymentToken.transfer(vaultStakingAddress, vaultCut); if (!success) revert ERR_POP_Trading_TokenTransferFailed(); /// The position id is later updated when the position is actually minted by the sequencer. @@ -234,8 +281,18 @@ contract Trading is Ownable { /// This event will be used to contruct the params when the sequencer will be minting the position. emit MintRequested(_msgSender(), nextMintRequestId.current()); + emit PositionStatus( + _msgSender(), + _productId, + 0, + nextMintRequestId.current(), + 0, + 1 + ); nextMintRequestId.increment(); + + return nextMintRequestId.current() - 1; } /// @notice The function called by the sequencer to mint the position. @@ -247,7 +304,7 @@ contract Trading is Ownable { function mintPositionSequencer( uint256 _requestId, uint256[] memory _positions - ) external onlySequencer returns (uint256) { + ) external onlySequencer nonReentrant returns (uint256) { MintRequest storage associatedRequest = mintRequestIdToStructure[ _requestId ]; @@ -260,12 +317,13 @@ contract Trading is Ownable { productToPositionContract[associatedRequest.productId] ); - uint256[] memory _multiplicator = products[associatedRequest.productId] - .multiplicatorBase; + Product storage associatedProduct = products[ + associatedRequest.productId + ]; uint256 positionId = positionContract.mint( _positions, - _multiplicator, + associatedProduct.multiplicatorBase, associatedRequest.receiver, associatedRequest.size, associatedRequest.strikeUpper, @@ -276,6 +334,15 @@ contract Trading is Ownable { associatedRequest.positionId = positionId; associatedRequest.isFullFilled = true; + emit PositionStatus( + associatedRequest.receiver, + associatedRequest.productId, + positionId, + _requestId, + 0, + 2 + ); + return positionId; } @@ -297,14 +364,18 @@ contract Trading is Ownable { bytes32 s, uint256 _owedFee, uint256 _toReturnFee - ) external validPosition(_productId, _positionId) { - IPositions positionContract = IPositions( - productToPositionContract[_productId] - ); - + ) + external + validPosition(_productId, _positionId) + nonReentrant + returns (uint256) + { /// Check if the caller even owns the position. - if (positionContract.getOwner(_positionId) != _msgSender()) - revert ERR_POP_Trading_NotThePositionOwner(); + if ( + IPositions(productToPositionContract[_productId]).getOwner( + _positionId + ) != _msgSender() + ) revert ERR_POP_Trading_NotThePositionOwner(); /// CHECK IF THE SIGNATURE ORIGINATED FROM THE SEQUENCER OR NOT. if ( @@ -312,15 +383,26 @@ contract Trading is Ownable { signatureUsed[_sequencerSignature] ) revert ERR_POP_Trading_SequencerSignatureInvalid(); - if (paymentToken.allowance(_msgSender(), address(this)) < _owedFee) - revert ERR_POP_Trading_InsufficientApprovedAmount(); + if ( + paymentToken.allowance(_msgSender(), address(this)) < + _owedFee + _toReturnFee + ) revert ERR_POP_Trading_InsufficientApprovedAmount(); + + if ( + !paymentToken.transferFrom( + _msgSender(), + address(this), + _owedFee + _toReturnFee + ) + ) revert ERR_POP_Trading_TokenTransferFailed(); + // Protocol collects [M(q + q′) −M(q)] ∗ fee - if (!paymentToken.transferFrom(_msgSender(), address(this), _owedFee)) - revert ERR_POP_Trading_TokenTransferFailed(); // Pay [M(q + q′) −M(q)] ∗ (1−fee) to user if (!paymentToken.transfer(_msgSender(), _toReturnFee)) revert ERR_POP_Trading_TokenTransferFailed(); + signatureUsed[_sequencerSignature] = true; + BurnRequest memory associatedRequest = BurnRequest({ burner: _msgSender(), productId: _productId, @@ -335,8 +417,18 @@ contract Trading is Ownable { ] = associatedRequest; emit BurnRequested(_msgSender(), nextBurnRequestId.current()); + emit PositionStatus( + _msgSender(), + _productId, + _positionId, + 0, + nextBurnRequestId.current(), + 3 + ); nextBurnRequestId.increment(); + + return nextBurnRequestId.current() - 1; } /// @notice The final function called by the sequencer to officially burn the position. @@ -346,7 +438,7 @@ contract Trading is Ownable { function burnPositionSequencer( uint256 _requestId, uint256[] memory _updatedPositions - ) external onlySequencer { + ) external onlySequencer nonReentrant { BurnRequest storage associatedRequest = burnRequestIdToStructure[ _requestId ]; @@ -360,7 +452,7 @@ contract Trading is Ownable { Product storage associatedProduct = products[ associatedRequest.productId ]; - uint256 limit = associatedProduct.limit; + uint256 limit = associatedProduct.intervals; for (uint256 i = 0; i < limit; i++) // Set qi = qi −q′i associatedProduct.supplyBase[i] = _updatedPositions[i]; @@ -370,6 +462,15 @@ contract Trading is Ownable { ); positionContract.burn(associatedRequest.positionId); + emit PositionStatus( + associatedRequest.burner, + associatedRequest.productId, + associatedRequest.positionId, + 0, + _requestId, + 4 + ); + associatedRequest.isFullFilled = true; } @@ -378,10 +479,23 @@ contract Trading is Ownable { /// @notice To get a product details based on its id. function getProduct( bytes32 _productId - ) public view returns (Product memory) { + ) external view returns (Product memory) { return products[_productId]; } + /// @notice To get a list of products together. + function getProducts( + bytes32[] memory _productIds, + uint256 _limit + ) external view returns (Product[] memory) { + Product[] memory toReturn = new Product[](_limit); + for (uint256 index = 0; index < _limit; index++) { + toReturn[index] = products[_productIds[index]]; + } + + return toReturn; + } + /// @notice Not required as of now. May need for the platform. dk. function getSlippageEstimate( uint256 _ask, @@ -420,11 +534,11 @@ contract Trading is Ownable { uint256[] memory supplyBase = currentProduct.supplyBase; uint256[] memory temp; - uint256 limit = currentProduct.limit; + uint256 limit = currentProduct.intervals; uint currentM = getM(supplyBase, temp, limit, false); - uint256 numerator = 10 ** DECIMALS * currentProduct.supply; + uint256 numerator = 10 ** DECIMALS * currentProduct.totalSupply; uint256 histogramValue = numerator / currentM; return histogramValue; } @@ -445,31 +559,90 @@ contract Trading is Ownable { sequencerAddress = _sequencer; } + function setVaultStaking(address _vaultStaking) external onlyOwner { + vaultStakingAddress = _vaultStaking; + } + + function setPaymentToken(address _newToken) external onlyOwner { + paymentToken = IERC20(_newToken); + } + /// @notice ADMIN FUNCTIONS ==================================================== + function bytes32ToString( + bytes32 _bytes32Data + ) public pure returns (string memory) { + bytes memory bytesData = new bytes(32); + for (uint i = 0; i < 32; i++) { + bytesData[i] = _bytes32Data[i]; + } + return string(bytesData); + } + /// @notice To add a new product with the provided params. + /// @dev IMPORTANT : In the product params, for the max/min price, You need to make sure + /// the difference of max and min is divisible by the intervals provided with no remainder. function addProduct( bytes32 _productId, - string memory _name, - string memory _symbol, + bytes32 _name, + bytes32 _symbol, Product memory _productParams ) external onlyOwner { - Product storage product = products[_productId]; + require(products[_productId].intervals == 0, "product-exists"); + + uint256 intervals = _productParams.intervals; + uint256 intervalSupply = _productParams.totalSupply / + _productParams.intervals; - require(product.limit == 0, "product-exists"); + if ( + intervalSupply * _productParams.intervals != + _productParams.totalSupply + ) revert ERR_POP_Trading_InvalidIntervalSupplyRatio(); + + uint256[] memory supplyBase; + uint256[] memory multiplicatorBase; + + // supplyBase[intervals - 1] = 0; + + assembly { + // Calculate the size of the array in bytes + let size := mul(intervals, 32) + // Allocate memory for the array + multiplicatorBase := mload(0x40) + // Set the length of the array + mstore(multiplicatorBase, intervals) + // Initialize all elements to 1 + for { + let i := 0 + } lt(i, intervals) { + i := add(i, 1) + } { + mstore(add(multiplicatorBase, mul(add(i, 1), 32)), 1) + } + // Update the free memory pointer + mstore(0x40, add(multiplicatorBase, add(size, 32))) + } + + assembly { + let size := mul(intervals, 32) + supplyBase := mload(0x40) + mstore(supplyBase, intervals) - uint256 _limit = _productParams.limit; - uint256[] memory multiplicatorBase = new uint256[](_limit); - uint256[] memory supplyBase = new uint256[](_limit); + for { + let i := 0 + } lt(i, intervals) { + i := add(i, 1) + } { + mstore(add(supplyBase, mul(add(i, 1), 32)), intervalSupply) + } - for (uint i = 0; i < _limit; i++) { - supplyBase[i] = 0; - multiplicatorBase[i] = 1; + mstore(0x40, add(supplyBase, add(size, 32))) } POP_Positions associatedPositionContract = new POP_Positions( _productId, - _name, - _symbol + bytes32ToString(_name), + bytes32ToString(_symbol), + intervals ); productToPositionContract[_productId] = address( @@ -479,8 +652,10 @@ contract Trading is Ownable { products[_productId] = Product({ supplyBase: supplyBase, multiplicatorBase: multiplicatorBase, - limit: _productParams.limit, - supply: _productParams.supply, + minPrice: _productParams.minPrice, + maxPrice: _productParams.maxPrice, + intervals: _productParams.intervals, + totalSupply: _productParams.totalSupply, margin: _productParams.margin, fee: _productParams.fee, positionContract: address(associatedPositionContract) @@ -489,15 +664,48 @@ contract Trading is Ownable { emit ProductAdded(_productId, _name, _symbol, products[_productId]); } + /// UPDATE FUNCTIONS ========================================= + /// @dev These should be allowed to be updated but not sure. + + /// @notice To update the supplyBase for a given product. + function updateSupplyBase( + bytes32 _productId, + uint256[] memory _newSupply, + uint256 _totalSupply + ) external isOperator validProduct(_productId) { + Product storage product = products[_productId]; + + for (uint i = 0; i < product.intervals; i++) { + product.supplyBase[i] = _newSupply[i]; + } + + product.totalSupply = _totalSupply; + } + + function updateMultiplicatorBase( + bytes32 _productId, + uint256[] memory _newMultiplicator + ) external isOperator validProduct(_productId) { + Product storage product = products[_productId]; + + for (uint i = 0; i < product.intervals; i++) { + product.multiplicatorBase[i] = _newMultiplicator[i]; + } + } + /// @notice Can be used to discontinue an asset by setting the limit to 0. function updateProduct( bytes32 _productId, Product memory _newProductParams - ) external onlyOwner { + ) external onlyOwner validProduct(_productId) { Product storage product = products[_productId]; - require(product.limit > 0, "Product-does-not-exist"); - product.supply = _newProductParams.supply; + product.supplyBase = _newProductParams.supplyBase; + product.multiplicatorBase = _newProductParams.multiplicatorBase; + product.minPrice = _newProductParams.minPrice; + product.maxPrice = _newProductParams.maxPrice; + product.intervals = _newProductParams.intervals; + product.totalSupply = _newProductParams.totalSupply; product.margin = _newProductParams.margin; product.fee = _newProductParams.fee; product.positionContract = _newProductParams.positionContract; @@ -507,12 +715,13 @@ contract Trading is Ownable { function transferTokensToVault() external onlyOwner { uint256 balance = paymentToken.balanceOf(address(this)); - paymentToken.transfer(vaultAddress, balance); + paymentToken.transfer(vaultStakingAddress, balance); } - // function transferETHToVault() external onlyOwner { - // uint256 balance = address(this).balance; - // } + function transferETHToVault() external onlyOwner { + uint256 balance = address(this).balance; + payable(vaultStakingAddress).transfer(balance); + } /// ================================================ diff --git a/hardhat.config.js b/hardhat.config.js index 225ccb8..3f2303f 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -68,8 +68,8 @@ module.exports = { gasPrice: 800000000000, timeout: 999999, }, - matictest: { - url: "https://matic-mumbai.chainstacklabs.com/", + mumbai: { + url: "https://rpc.ankr.com/polygon_mumbai", accounts: [process.env.private_key], timeout: 999999, }, diff --git a/package.json b/package.json index 55c27c7..2d48070 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,12 @@ "scripts": { "test": "npx hardhat test", "compile": "npx hardhat compile", - "deploy": "hardhat run scripts/deployAccumulation.js --network matictest", - "deploy-test": "hardhat run scripts/0_deployAndTestTrading.js", - "deploy-accumulation": "hardhat run --network matictest scripts/deployAccumulation.js", - "deploy-faucets": "hardhat run --network matictest scripts/deployFaucet.js", - "verify-contract-faucet": "hardhat verify --contract contracts/ERC20Faucet.sol:FaucetToken --network matictest 0xE118429D095de1a93951c67D04B523fE5cbAB62c", - "verify-contract-sleep": "hardhat verify --contract contracts/ERC20Sleep.sol:SleepToken --network matictest 0xb94d207a3fBdb312cef2e5dBEb7C22A76516BE37", - "verify-contract-accumulation": "hardhat verify --network matictest 0x88f07B2eEE714bc2133eA9F973E76c9724b66042 0xE118429D095de1a93951c67D04B523fE5cbAB62c 0xE592427A0AEce92De3Edee1F18E0157C05861564", + "deploy-trading": "npx hardhat run --network mumbai scripts/deployTrading.js", + "verify-trading": "hardhat run --network mumbai scripts/verifyTrading.js", + "add-products": "hardhat run --network mumbai scripts/addProducts.js", + "request-mint": "hardhat run --network mumbai scripts/requestPosition.js", + "sequencer-mint": "hardhat run --network mumbai scripts/mintSeq.js", + "request-burn": "hardhat run --network mumbai scripts/requestBurn.js", "clean": "hardhat clean" }, "devDependencies": { diff --git a/scripts/0_deployAndTestTrading.js b/scripts/0_deployAndTestTrading.js index 1cb97fd..2439475 100644 --- a/scripts/0_deployAndTestTrading.js +++ b/scripts/0_deployAndTestTrading.js @@ -22,12 +22,7 @@ module.exports = async ({ deployments, getNamedAccounts, ethers }) => { const Trading = await deploy("Trading", { from: deployer, - args: [ - Mock.address, - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000", - ], + args: [Mock.address, deployer, deployer, deployer], }); console.log("Deployed Trading at :", Trading.address); @@ -50,11 +45,11 @@ module.exports = async ({ deployments, getNamedAccounts, ethers }) => { const receipt = await txn.wait(1); // Check the product creation params are same in the event emitted. - console.log( - "Product created! Emitted values :", - receipt.events[1].args[3], - "\n" - ); + // console.log( + // "Product created! Emitted values :", + // receipt.events[1].args[3], + // "\n" + // ); // Product Contract Exists : Should != 0 and pass. const productAdded = await trading.getProduct(productId); @@ -83,24 +78,24 @@ module.exports = async ({ deployments, getNamedAccounts, ethers }) => { // ); // Update the Product by Owner : Should Pass. - const initialFee = productAdded.fee; - console.log("\nCurrent fee :", initialFee); + const fee = productAdded.fee; + console.log("Current fee :", fee); // Only works for supply/margin/fee/positionContract as of v1. Can be changed if the client asks for it. - var updatedParams = { - supplyBase: [], - multiplicatorBase: [], - limit: 10, - supply: 1000, - margin: 100, - fee: 2000, - positionContract: "0x0000000000000000000000000000000000000000", - }; + // var updatedParams = { + // supplyBase: [], + // multiplicatorBase: [], + // limit: 10, + // supply: 1000, + // margin: 100, + // fee: 2000, + // positionContract: "0x0000000000000000000000000000000000000000", + // }; - await trading.updateProduct(productId, updatedParams); - const productUpdated = await trading.getProduct(productId); - const updatedFee = productUpdated.fee; - console.log("Final fee :", updatedFee); + // await trading.updateProduct(productId, updatedParams); + // const productUpdated = await trading.getProduct(productId); + // const updatedFee = productUpdated.fee; + // console.log("Final fee :", updatedFee); // Reverts if non-user tries updating the product : Fails with : VM Exception while processing transaction: //reverted with reason string 'Ownable: caller is not the owner'". @@ -119,10 +114,38 @@ module.exports = async ({ deployments, getNamedAccounts, ethers }) => { // Should fail for updating non-existent products : Does fail with VM Exception while processing // transaction: reverted with reason string 'Product-does-not-exist'" - const nonExistingProductId = ethers.utils.formatBytes32String( - "NON_EXISTING_PRODUCT" - ); - await trading.updateProduct(nonExistingProductId, updatedParams); + // const nonExistingProductId = ethers.utils.formatBytes32String( + // "NON_EXISTING_PRODUCT" + // ); + // await trading.updateProduct(nonExistingProductId, updatedParams); + + const size = ethers.BigNumber.from("10"); + const maxfee = ethers.BigNumber.from("1000000"); + + const strikeLower = 100; + const strikeUpper = 200; + + const protocolCut = size * fee; + const vaultCut = size * (maxfee - fee); + const toApprove = (protocolCut + vaultCut).toString(); + // console.log(toApprove.toString()); + // console.log(protocolCut, vaultCut); + + await mock.approve(Trading.address, toApprove); + + await trading.requestPosition(productId, size, strikeLower, strikeUpper); + + const mintRequest = await trading.mintRequestIdToStructure(1); + console.log(mintRequest); + // receiver: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + // productId: '0x50524f445543545f310000000000000000000000000000000000000000000000', + // positionId: BigNumber { value: "0" }, + // size: BigNumber { value: "10" }, + // strikeLower: BigNumber { value: "100" }, + // strikeUpper: BigNumber { value: "200" }, + // totalFee: BigNumber { value: "10000000" }, + // isFullFilled: false }; module.exports.tags = ["All"]; +//0 \ No newline at end of file diff --git a/scripts/addProducts.js b/scripts/addProducts.js new file mode 100644 index 0000000..935913c --- /dev/null +++ b/scripts/addProducts.js @@ -0,0 +1,44 @@ +const { ethers } = require("hardhat"); + +async function main() { + const TradingFact = await ethers.getContractFactory("POP_Trading"); + + const tradingContractInstance = TradingFact.attach( + "0xBD4B78B3968922e8A53F1d845eB3a128Adc2aA12" + ); + + console.log("tradingContract loaded at:", tradingContractInstance.address); + + for (let i = 4; i <= 10; i++) { + const productId = ethers.utils.formatBytes32String(`PRODUCT_${i}`); + const name = `Product ${i}`; + const symbol = `P${i}`; + const productParams = { + supplyBase: [0, 0, 0], + multiplicatorBase: [1, 1, 1], + limit: 3, + supply: 1000, + margin: 100, + fee: 5000, + positionContract: "0x0000000000000000000000000000000000000000", + }; + + await tradingContractInstance.addProduct( + productId, + name, + symbol, + productParams + ); + + const product = await tradingContractInstance.getProduct(productId); + console.log("product added ", product); + } +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +//0 \ No newline at end of file diff --git a/scripts/deployContracts.js b/scripts/deployContracts.js index 9d6d24d..a4e8e6a 100644 --- a/scripts/deployContracts.js +++ b/scripts/deployContracts.js @@ -22,3 +22,4 @@ main().catch((error) => { console.error(error); process.exitCode = 1; }); +//0 \ No newline at end of file diff --git a/scripts/deployFaucet.js b/scripts/deployFaucet.js index c2a1661..2aecda8 100644 --- a/scripts/deployFaucet.js +++ b/scripts/deployFaucet.js @@ -23,3 +23,4 @@ main().catch((error) => { console.error(error); process.exitCode = 1; }); +//0 \ No newline at end of file diff --git a/scripts/deployGrid.js b/scripts/deployGrid.js index ea1f143..7d878ad 100644 --- a/scripts/deployGrid.js +++ b/scripts/deployGrid.js @@ -13,3 +13,4 @@ main().catch((error) => { console.error(error); process.exitCode = 1; }); +//0 \ No newline at end of file diff --git a/scripts/deployStaking.js b/scripts/deployStaking.js index 3c8d94b..ebdc48d 100644 --- a/scripts/deployStaking.js +++ b/scripts/deployStaking.js @@ -26,3 +26,4 @@ main().catch((error) => { console.error(error); process.exitCode = 1; }); +//0 \ No newline at end of file diff --git a/scripts/deployTrading.js b/scripts/deployTrading.js new file mode 100644 index 0000000..9ad972b --- /dev/null +++ b/scripts/deployTrading.js @@ -0,0 +1,25 @@ +const { ethers } = require("hardhat"); + +async function main() { + const TradingFact = await ethers.getContractFactory("POP_Trading"); + + const usdcFaucet = "0x2ddb853a09d4Da8f0191c5B887541CD7af3dDdce"; + const sequencer = "0x8BD0e959E9a7273D465ac74d427Ecc8AAaCa55D8"; + const stakingContract = "0xd9329eA2f5e4f9942872490b185f17e442122f28"; + + const tradingContract = await TradingFact.deploy( + usdcFaucet, + sequencer, + stakingContract + ); + await tradingContract.deployed(); + console.log("tradingContract deployed at:", tradingContract.address); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +//0 \ No newline at end of file diff --git a/scripts/lambda.js b/scripts/lambda.js new file mode 100644 index 0000000..24573e6 --- /dev/null +++ b/scripts/lambda.js @@ -0,0 +1,162 @@ +function zeros(length) { + return new Array(length).fill(0); +} + +function linspace(start, stop, num) { + const step = (stop - start) / (num - 1); + const arr = []; + for (let i = 0; i < num; i++) { + arr.push(start + i * step); + } + return arr; +} + +function sum(arr) { + return arr.reduce((acc, val) => acc + val, 0); +} + +function sqrt(val) { + return Math.sqrt(val); +} + +function OutOfBoundsError(message) { + const error = {}; //new Error(message); + error.name = "OutOfBoundsError"; + return error; +} + +function NFT(positions, multiplicator) { + return { positions, multiplicator }; +} + +function PerpetualOptionsProtocol(startPrice, endPrice, arrayLength) { + const qWeightsArray = zeros(arrayLength); + const multiplicatorArray = linspace(1, 1, arrayLength); + + const priceArray = linspace(startPrice, endPrice, arrayLength); + const priceStepSize = (endPrice - startPrice) / (arrayLength - 1); + + function _multiplicator(weightsArray) { + const sumOfWeightsSquared = sum(weightsArray.map((w) => w ** 2)); + const mValue = sqrt(sumOfWeightsSquared); + return mValue; + } + + function lambdaCalculation(r1, r2, s, fee) { + const factor = r2 - r1 + 1; + const mValue = _multiplicator(qWeightsArray); + + const sumQArray = sum(qWeightsArray.slice(r1, r2)); + const numerator = + -sumQArray + + sqrt( + sumQArray ** 2 + factor * ((mValue + s * (1 - fee)) ** 2 - mValue ** 2) + ); + const lambdaOutput = numerator / factor; + return lambdaOutput; + } + + function mint(r1, r2, s, fee) { + const positions = zeros(qWeightsArray.length); + const multiplicator = multiplicatorArray.slice(); + const newNFT = NFT(positions, multiplicator); + + for (let i = r1; i < r2; i++) { + newNFT.positions[i] = lambdaCalculation(r1, r2, s, fee); + } + + const collectedFee = s * fee; + const transferredFee = s * (1 - fee); + + return [newNFT, collectedFee, transferredFee]; + } + + function burn(nftToBurn, fraction, fee) { + const qDashedWeightsArray = nftToBurn.positions.map( + (pos, i) => + (fraction * pos * multiplicatorArray[i]) / nftToBurn.multiplicator[i] + ); + + qWeightsArray.forEach((_, i) => { + qWeightsArray[i] -= qDashedWeightsArray[i]; + }); + + const multiplicatorSum = _multiplicator( + qWeightsArray.map((w, i) => w + qDashedWeightsArray[i]) + ); + const multiplicatorQ = _multiplicator(qWeightsArray); + const multiplicatorDiff = multiplicatorSum - multiplicatorQ; + + const collectedFee = multiplicatorDiff * fee; + const transferredFee = multiplicatorDiff * (1 - fee); + + return [collectedFee, transferredFee]; + } + + function lambdaPriceFeedCalculation(currentTick, alpha) { + const mValue = _multiplicator(qWeightsArray); + const sumQSquared = sum(qWeightsArray.map((w) => w ** 2)); + const qCurrentTick = qWeightsArray[currentTick]; + + if (qCurrentTick === 0.0) { + // throw new Error( + // "The weight of q at the current tick is zero, but we're trying to divide by it." + // ); + } + + const mValueSquared = mValue ** 2; + const alphaSquared = alpha ** 2; + const qCurrentTickSquared = qCurrentTick ** 2; + const lambdaValue = sqrt( + mValueSquared - alphaSquared * (sumQSquared - qCurrentTickSquared) + ); + return lambdaValue / qCurrentTick; + } + + function priceFeed(priceFeedValue, alpha) { + if (priceFeedValue < startPrice || priceFeedValue > endPrice) { + throw new OutOfBoundsError( + `Current ${priceFeedValue} is out of bounds: ${startPrice} / ${endPrice}` + ); + } + + const currentTick = Math.min( + Math.floor((priceFeedValue - startPrice) / priceStepSize), + arrayLength - 1 + ); + + const lambdaValue = lambdaPriceFeedCalculation(currentTick, alpha); + + qWeightsArray.forEach((_, i) => { + qWeightsArray[i] *= alpha; + }); + + qWeightsArray[currentTick] *= lambdaValue; + + multiplicatorArray.forEach((_, i) => { + multiplicatorArray[i] *= alpha; + }); + + multiplicatorArray[currentTick] *= lambdaValue; + } + + return { + mint, + burn, + priceFeed, + multiplicatorArray, + lambdaCalculation, + lambdaPriceFeedCalculation, + }; +} + +// const testPerpetuals = PerpetualOptionsProtocol(0.0, 10.0, 11); +// const output = testPerpetuals.console.log("MINT OUTPUT"); +// console.log(output); +// // // const burnOutput = testPerpetuals.burn(output[0], 1.0, 0.5); +// // // console.log("BURN OUTPUT"); +// // // console.log(burnOutput); +// // testPerpetuals.priceFeed(1.2, 0.5); + +module.exports = { PerpetualOptionsProtocol }; +//a \ No newline at end of file diff --git a/scripts/mintSeq.js b/scripts/mintSeq.js new file mode 100644 index 0000000..dd71a5b --- /dev/null +++ b/scripts/mintSeq.js @@ -0,0 +1,45 @@ +const { ethers } = require("hardhat"); +const { PerpetualOptionsProtocol } = require("./lambda"); + +async function main() { + const TradingFact = await ethers.getContractFactory("POP_Trading"); + + const tradingContractInstance = TradingFact.attach( + "0xBD4B78B3968922e8A53F1d845eB3a128Adc2aA12" + ); + + const productId = ethers.utils.formatBytes32String("PRODUCT_2"); + const productAdded = await tradingContractInstance.getProduct(productId); + + const fee = productAdded.fee; + + const size = "10"; + const maxfee = "1000000"; + const strikeLower = 100; + const strikeUpper = 200; + + const requestId = "2"; + const limit = 5; + + const helper = PerpetualOptionsProtocol(strikeLower, strikeUpper, limit); + + console.log("helpers ", helper); + const positions = helper.mint(strikeLower, strikeUpper, size, fee); + + console.log("positions array ", positions); + + const trx = await tradingContractInstance.mintPositionSequencer( + requestId, + positions + ); + + console.log("req trx", trx); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +//0 \ No newline at end of file diff --git a/scripts/requestBurn.js b/scripts/requestBurn.js new file mode 100644 index 0000000..67ad18d --- /dev/null +++ b/scripts/requestBurn.js @@ -0,0 +1,99 @@ +const { ethers } = require("hardhat"); + +async function getSequencerRSV(positionId, productId) { + try { + const owner = ethers.getSigner(); + + // const message = `Position ${positionId} Product ${productId}`; + + const payload = ethers.utils.defaultAbiCoder.encode( + ["string", "bytes32", "string", "uint256"], + ["Product:", productId, "Position:", positionId] + ); + const payloadHash = ethers.utils.keccak256(payload); + + const toCheck = ethers.utils.solidityKeccak256( + ["string", "bytes32"], + ["\x19Ethereum Signed Message:\n32", payloadHash] + ); + console.log(toCheck); + + // Sign the message using the wallet + const signature = await owner.signMessage( + ethers.utils.arrayify(payloadHash) + ); + + const sig = ethers.utils.splitSignature(signature); + const r = sig.r; + const s = sig.s; + const v = sig.v; + + return [toCheck, r, s, v]; + } catch (err) { + console.error(err); + } +} + +async function main() { + const TradingFact = await ethers.getContractFactory("POP_Trading"); + + const tradingContractInstance = TradingFact.attach( + "0xBD4B78B3968922e8A53F1d845eB3a128Adc2aA12" + ); + + const UsdcFact = await ethers.getContractFactory("MockUSDC"); + const usdc = UsdcFact.attach("0x2ddb853a09d4Da8f0191c5B887541CD7af3dDdce"); + + // Approve the Trading contract to spend payment tokens on behalf of the user + + // const productId = ethers.utils.formatBytes32String("PRODUCT_1"); + // const productAdded = await tradingContractInstance.getProduct(productId); + + // ASSUMING THE FEE FOR NOW. WILL BE CALCULATED ON THE FRONTEND. + const owed = "1000000"; + const toReturn = "200000"; + const approveSuccess = await usdc.approve( + tradingContractInstance.address, + owed + ); + + // already minted nft + const positionPair = + "0x0bc97c0fcc383ee8dea670dd25422c5ee35783724cc8d53729a7bef1b3dcfce6"; + const decoded = ethers.utils.defaultAbiCoder.decode( + ["bytes32", "bytes32"], + positionPair + ); + + const productId = decoded[0]; + const positionId = decoded[1]; + + console.log("productId", productId); + console.log("positionId", positionId); + + // const [toCheck, r, s, v] = await getSequencerRSV(requestId, productId); + + // const txn = await tradingContractInstance.requestBurn( + // productId, + // requestId, + // toCheck, + // v, + // r, + // s, + // owed, + // toReturn, + // { + // gasLimit: 10000000, + // } + // ); + + console.log("req trx", trx); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +//0 \ No newline at end of file diff --git a/scripts/requestPosition.js b/scripts/requestPosition.js new file mode 100644 index 0000000..5825f43 --- /dev/null +++ b/scripts/requestPosition.js @@ -0,0 +1,47 @@ +const { ethers } = require("hardhat"); + +async function main() { + const TradingFact = await ethers.getContractFactory("POP_Trading"); + + const tradingContractInstance = TradingFact.attach( + "0xBD4B78B3968922e8A53F1d845eB3a128Adc2aA12" + ); + + const UsdcFact = await ethers.getContractFactory("MockUSDC"); + const usdc = UsdcFact.attach("0x2ddb853a09d4Da8f0191c5B887541CD7af3dDdce"); + + // Approve the Trading contract to spend payment tokens on behalf of the user + + const productId = ethers.utils.formatBytes32String("PRODUCT_2"); + const productAdded = await tradingContractInstance.getProduct(productId); + + const fee = productAdded.fee; + const size = "10"; + const maxfee = "1000000"; + const strikeLower = 100; + const strikeUpper = 200; + + const protocolCut = size * fee; + const vaultCut = size * (maxfee - fee); + const toApprove = (protocolCut + vaultCut).toString(); + + await usdc.approve(tradingContractInstance.address, toApprove); + + // Request a position + const trx = await tradingContractInstance.requestPosition( + productId, + size, + strikeLower, + strikeUpper + ); + + console.log("req trx", trx); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +//0 \ No newline at end of file diff --git a/scripts/verifyTrading.js b/scripts/verifyTrading.js new file mode 100644 index 0000000..aab85a7 --- /dev/null +++ b/scripts/verifyTrading.js @@ -0,0 +1,25 @@ +const hre = require("hardhat"); + +async function main() { + const usdcFaucet = "0x2ddb853a09d4Da8f0191c5B887541CD7af3dDdce"; + const sequencer = "0x8BD0e959E9a7273D465ac74d427Ecc8AAaCa55D8"; + const stakingContract = "0xd9329eA2f5e4f9942872490b185f17e442122f28"; + + const latestDeployedAddress = "0xBD4B78B3968922e8A53F1d845eB3a128Adc2aA12"; + const deployParam = [usdcFaucet, sequencer, stakingContract]; + + await hre.run("verify:verify", { + address: latestDeployedAddress, + constructorArguments: [...deployParam], + }); + + console.log("tradingContract verired at:", latestDeployedAddress); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +//0 \ No newline at end of file diff --git a/scripts/workflow/addProduct.js b/scripts/workflow/addProduct.js new file mode 100644 index 0000000..c933a8b --- /dev/null +++ b/scripts/workflow/addProduct.js @@ -0,0 +1,63 @@ +const path = require("path"); +require("dotenv").config({ path: path.join(__dirname, "..", ".env") }); + +const { + address: tradingAddress, + abi: tradingABI, +} = require("../deployments/mumbai/POP_Trading.json"); + +const { ethers } = require("ethers"); + +const PROVIDER = process.env.MUMBAI_RPC; +const DEPLOYER = process.env.PK_DEPLOYER; + +const provider = new ethers.providers.JsonRpcProvider(PROVIDER); +const wallet = new ethers.Wallet(DEPLOYER, provider); + +const trading = new ethers.Contract(tradingAddress, tradingABI, wallet); + +async function create( + productIdInString, + productNameInString, + productSymbolInString, + productParams +) { + try { + const productId = ethers.utils.formatBytes32String(productIdInString); + const productName = ethers.utils.formatBytes32String(productNameInString); + const productSymbol = ethers.utils.formatBytes32String( + productSymbolInString + ); + + const txn = await trading.addProduct( + productId, + productName, + productSymbol, + productParams + ); + const receipt = await txn.wait(1); + console.log(receipt); + return receipt; + } catch (err) { + console.error(err); + } +} + +async function main() { + const productIdBase = "PRODUCT 3"; + const productName = "Chainlink"; + const productSymbol = "LINK"; + const productParams = { + supplyBase: [], + multiplicatorBase: [], + limit: 500, + supply: 1000000, + margin: 1000, + fee: 2000, + positionContract: "0x0000000000000000000000000000000000000000", + }; + + await create(productIdBase, productName, productSymbol, productParams); +} + +main(); diff --git a/scripts/workflow/address.txt b/scripts/workflow/address.txt new file mode 100644 index 0000000..03f434e --- /dev/null +++ b/scripts/workflow/address.txt @@ -0,0 +1,9 @@ +Deployed USDC Mocks at : 0x2ddb853a09d4Da8f0191c5B887541CD7af3dDdce +Deployed Stake Token at : 0x8fF31C69CbcE7d4200A0bB20c7902FAd8A0619D0 +Deployed Reward Token at : 0xBC1c13C263b1F1652B018505E68C7f966dfB8309 +Deployed Trading at : 0xA3082D80B119f6Bc3DC39cf98380898B26c0b6b9 +Deployed Staking at : 0xd9329eA2f5e4f9942872490b185f17e442122f28 +Position contract add : 0x244bf2266debE7134b5d5cB5A7eEaB4d8f7eEA5E + +Set Sequencer Address : 0xaa969EbE867508922a5D095AD89325c51701Ac5D +Set Staking Address : 0xd9329eA2f5e4f9942872490b185f17e442122f28 \ No newline at end of file diff --git a/scripts/workflow/burnRequest.js b/scripts/workflow/burnRequest.js new file mode 100644 index 0000000..90bbbf5 --- /dev/null +++ b/scripts/workflow/burnRequest.js @@ -0,0 +1,99 @@ +const path = require("path"); +require("dotenv").config({ path: path.join(__dirname, "..", ".env") }); + +const { + address: tradingAddress, + abi: tradingABI, +} = require("../deployments/mumbai/POP_Trading.json"); + +const { + address: usdcAddress, + abi: usdcABI, +} = require("../deployments/mumbai/MockUSDC.json"); + +const { ethers } = require("ethers"); + +const PROVIDER = process.env.MUMBAI_RPC; +const SEQUENCER = process.env.PK_SEQUENCER; +const DEPLOYER = process.env.PK_DEPLOYER; + +const provider = new ethers.providers.JsonRpcProvider(PROVIDER); + +const walletSequencer = new ethers.Wallet(SEQUENCER, provider); +const wallet = new ethers.Wallet(DEPLOYER, provider); + +const mock = new ethers.Contract(usdcAddress, usdcABI, wallet); +const trading = new ethers.Contract(tradingAddress, tradingABI, wallet); + +async function getSequencerRSV(positionId, productId) { + try { + // const message = `Position ${positionId} Product ${productId}`; + + const payload = ethers.utils.defaultAbiCoder.encode( + ["string", "bytes32", "string", "uint256"], + ["Product:", productId, "Position:", positionId] + ); + const payloadHash = ethers.utils.keccak256(payload); + + const toCheck = ethers.utils.solidityKeccak256( + ["string", "bytes32"], + ["\x19Ethereum Signed Message:\n32", payloadHash] + ); + console.log(toCheck); + + // Sign the message using the wallet + const signature = await walletSequencer.signMessage( + ethers.utils.arrayify(payloadHash) + ); + + const sig = ethers.utils.splitSignature(signature); + const r = sig.r; + const s = sig.s; + const v = sig.v; + + return [toCheck, r, s, v]; + } catch (err) { + console.error(err); + } +} + +async function approveTokens(amount) { + try { + const txn = await mock.approve(tradingAddress, amount); + const receipt = await txn.wait(1); + + return receipt; + } catch (err) {} +} + +async function main() { + // ASSUMING THE FEE FOR NOW. WILL BE CALCULATED ON THE FRONTEND. + const owed = "1000000"; + const toReturn = "200000"; + const approveSuccess = await approveTokens(owed); + + const productId = + "0x50524f445543545f310000000000000000000000000000000000000000000000"; + const positionId = "1"; + + const [toCheck, r, s, v] = await getSequencerRSV(positionId, productId); + + const txn = await trading.requestBurn( + productId, + positionId, + toCheck, + v, + r, + s, + owed, + toReturn, + { + gasLimit: 10000000, + } + ); + const receipt = await txn.wait(1); + console.log(receipt); + // console.log(r, s, v); +} + +main(); diff --git a/scripts/workflow/burnRequestStructure.js b/scripts/workflow/burnRequestStructure.js new file mode 100644 index 0000000..561e2c0 --- /dev/null +++ b/scripts/workflow/burnRequestStructure.js @@ -0,0 +1,24 @@ +const path = require("path"); +require("dotenv").config({ path: path.join(__dirname, "..", ".env") }); + +const { + address: tradingAddress, + abi: tradingABI, +} = require("../deployments/mumbai/POP_Trading.json"); + +const { ethers } = require("ethers"); + +const PROVIDER = process.env.MUMBAI_RPC; +const DEPLOYER = process.env.PK_DEPLOYER; + +const provider = new ethers.providers.JsonRpcProvider(PROVIDER); +const wallet = new ethers.Wallet(DEPLOYER, provider); + +const trading = new ethers.Contract(tradingAddress, tradingABI, wallet); + +async function getRequestIdStructure(id) { + const burnRequest = await trading.burnRequestIdToStructure(id); + console.log(burnRequest); +} + +getRequestIdStructure(1); diff --git a/scripts/workflow/burnSequencer.js b/scripts/workflow/burnSequencer.js new file mode 100644 index 0000000..e69de29 diff --git a/scripts/workflow/calculateAWR.js b/scripts/workflow/calculateAWR.js new file mode 100644 index 0000000..e69de29 diff --git a/scripts/workflow/checkSetRelatedContracts.js b/scripts/workflow/checkSetRelatedContracts.js new file mode 100644 index 0000000..7bf6c5d --- /dev/null +++ b/scripts/workflow/checkSetRelatedContracts.js @@ -0,0 +1,58 @@ +const path = require("path"); +require("dotenv").config({ path: path.join(__dirname, "..", ".env") }); + +const { + address: tradingAddress, + abi: tradingABI, +} = require("../deployments/mumbai/POP_Trading.json"); + +const { ethers } = require("ethers"); + +const PROVIDER = process.env.MUMBAI_RPC; +const OWNER = process.env.PK_DEPLOYER; + +const provider = new ethers.providers.JsonRpcProvider(PROVIDER); +const wallet = new ethers.Wallet(OWNER, provider); + +async function getRelatedContracts() { + try { + const contract = new ethers.Contract(tradingAddress, tradingABI, wallet); + const sequencerAddress = await contract.sequencerAddress(); + const stakingAddress = await contract.vaultStakingAddress(); + console.log("Addresses result :", sequencerAddress, stakingAddress); + } catch (error) { + console.error("Error calling test function:", error); + } +} + +async function setSeq() { + try { + const contract = new ethers.Contract(tradingAddress, tradingABI, wallet); + const newSeq = process.env.PUB_SEQUENCER; + const txn = await contract.setSequencer(newSeq); + await txn.wait(1); + + const sequencerAddress = await contract.sequencerAddress(); + console.log("New Sequencer result:", sequencerAddress); + } catch (error) { + console.error("Error calling test function:", error); + } +} + +async function setStk() { + try { + const contract = new ethers.Contract(tradingAddress, tradingABI, wallet); + const newStk = process.env.PUB_VAULTSTAKING; + const txn = await contract.setVaultStaking(newStk); + await txn.wait(1); + + const stakingAddress = await contract.vaultStakingAddress(); + console.log("New Vault Staking result:", stakingAddress); + } catch (error) { + console.error("Error calling test function:", error); + } +} + +getRelatedContracts(); +// setSeq(); +// setStk(); diff --git a/scripts/workflow/getBurnFee.js b/scripts/workflow/getBurnFee.js new file mode 100644 index 0000000..12758da --- /dev/null +++ b/scripts/workflow/getBurnFee.js @@ -0,0 +1,177 @@ +const path = require("path"); +require("dotenv").config({ path: path.join(__dirname, "..", ".env") }); + +const { + address: tradingAddress, + abi: tradingABI, +} = require("../deployments/mumbai/POP_Trading.json"); + +const { + abi: positionABI, +} = require("../artifacts/contracts/Positions.sol/POP_Positions.json"); + +const { ethers } = require("ethers"); + +const PROVIDER = process.env.MUMBAI_RPC; +const DEPLOYER = process.env.PK_DEPLOYER; + +const provider = new ethers.providers.JsonRpcProvider(PROVIDER); +const wallet = new ethers.Wallet(DEPLOYER, provider); + +const trading = new ethers.Contract(tradingAddress, tradingABI, wallet); +// The position contract address is sent with the api call. We will use an exisiting address here for mock purpose. +const positionAddress = "0xF6F142Cd0AE69c42A4774C1a5bfc561d678A57e9"; +const positions = new ethers.Contract(positionAddress, positionABI, wallet); + +function getM(array, size) { + if (size <= 0) { + throw new Error("Size must be a positive integer."); + } + + let sumOfSquares = 0; + for (let i = 0; i < size; i++) { + sumOfSquares += array[i] ** 2; + } + + const sqrtResult = Math.sqrt(sumOfSquares); + return sqrtResult; +} + +async function sumArrays(array1, array2) { + if (array1.length !== array2.length) { + throw new Error("Arrays must have the same length."); + } + + const sumArray = []; + + for (let i = 0; i < array1.length; i++) { + sumArray.push(array1[i] + array2[i]); + } + + return sumArray; +} + +// Pay [M(q + q′) −M(q)] ∗(1 −fee) to user +async function getReturnFee(qi, qiDash, fee) { + const sumArray = await sumArrays(qi, qiDash); + + const a = getM(sumArray, sumArray.length); + const b = getM(qi, qi.length); + + const finalFee = fee / 1000000; + const final = (a - b) * (1 - finalFee); + + return Math.floor(final * 1000000); +} + +// Protocol collects [M(q + q′) −M(q)] ∗fee +async function getOwedFee(qi, qiDash, fee) { + const sumArray = await sumArrays(qi, qiDash); + + const a = getM(sumArray, sumArray.length); + const b = getM(qi, qi.length); + + const finalFee = fee / 1000000; + const final = (a - b) * finalFee; + + return Math.floor(final * 1000000); +} + +async function getQIDashArray( + f, + supply, + nftPosition, + multiplicator, + nftMultiplicator, + size +) { + const finalArray = new Array(Number(size)).fill(0); + + for (var i = 0; i < size; i++) { + const qiDash = + (f * nftPosition[i] * multiplicator[i]) / nftMultiplicator[i]; + finalArray[i] = qiDash; + } + + console.log("QI DASH : ", finalArray); + return finalArray; +} + +async function getQIArray(supply, qiDash, size) { + const finalArray = new Array(Number(size)).fill(0); + for (var i = 0; i < size; i++) { + const qi = supply[i] - qiDash[i]; + finalArray[i] = qi; + } + console.log("QI ARRAY : ", finalArray); + return finalArray; +} + +async function makeCompatible(array, size) { + const nodeCompatibleArray = []; + for (let i = 0; i < size; i++) { + const convertedValue = array[i].toString(); // or .toNumber() + nodeCompatibleArray.push(Number(convertedValue)); + } + + return nodeCompatibleArray; +} + +async function main() { + const id = 1; + const positionDetails = await positions.getPosition(id); + + var nftPosition = positionDetails.position; + nftPosition = await makeCompatible(nftPosition, nftPosition.length); + console.log("NFT POSITION :", nftPosition); + + var nftMultiplicator = positionDetails.multiplicator; + nftMultiplicator = await makeCompatible( + nftMultiplicator, + nftMultiplicator.length + ); + console.log("NFT MULTIPLICATOR :", nftMultiplicator); + + const productId = + "0x50524f445543545f310000000000000000000000000000000000000000000000"; + const productDetails = await trading.getProduct(productId); + + const fee = productDetails.fee; + console.log("Fee :", fee.toString); + + var supply = productDetails.supplyBase; + supply = await makeCompatible(supply, supply.length); + console.log("SUPPLY :", supply); + + var multiplicator = productDetails.multiplicatorBase; + multiplicator = await makeCompatible(multiplicator, multiplicator.length); + console.log("MULTIPLICATOR :", multiplicator); + + const fraction = 20; + + const qiDashArray = await getQIDashArray( + fraction, + supply, + nftPosition, + multiplicator, + nftMultiplicator, + nftMultiplicator.length + ); + + const qiArray = await getQIArray(supply, qiDashArray, supply.length); + + const toReturnFee = await getReturnFee( + qiArray, + qiDashArray, + Number(fee.toString()) + ); + const owedFee = await getOwedFee( + qiArray, + qiDashArray, + Number(fee.toString()) + ); + + console.log("To Return :", toReturnFee); + console.log("Owed :", owedFee); +} +main(); diff --git a/scripts/workflow/getPositionDetails.js b/scripts/workflow/getPositionDetails.js new file mode 100644 index 0000000..786c717 --- /dev/null +++ b/scripts/workflow/getPositionDetails.js @@ -0,0 +1,33 @@ +const path = require("path"); +require("dotenv").config({ path: path.join(__dirname, "..", ".env") }); + +// The address is for the PRODUCT 1 +const positionAddress = "0xF6F142Cd0AE69c42A4774C1a5bfc561d678A57e9"; +const { + abi: positionABI, +} = require("../artifacts/contracts/Positions.sol/POP_Positions.json"); + +const { ethers } = require("ethers"); + +const PROVIDER = process.env.MUMBAI_RPC; +const OWNER = process.env.PK_DEPLOYER; + +const provider = new ethers.providers.JsonRpcProvider(PROVIDER); +const wallet = new ethers.Wallet(OWNER, provider); + +const positions = new ethers.Contract(positionAddress, positionABI, wallet); + +async function getPositionDetails(id) { + try { + const position = await positions.getPosition(id); + console.log(position); + } catch (err) { + console.log(err); + } +} + +async function main() { + await getPositionDetails(1); +} + +main(); diff --git a/scripts/workflow/getProductDetails.js b/scripts/workflow/getProductDetails.js new file mode 100644 index 0000000..b90cc2c --- /dev/null +++ b/scripts/workflow/getProductDetails.js @@ -0,0 +1,30 @@ +const path = require("path"); +require("dotenv").config({ path: path.join(__dirname, "..", ".env") }); + +const { + address: tradingAddress, + abi: tradingABI, +} = require("../deployments/mumbai/POP_Trading.json"); + +const { ethers } = require("ethers"); + +const PROVIDER = process.env.MUMBAI_RPC; +const DEPLOYER = process.env.PK_DEPLOYER; + +const provider = new ethers.providers.JsonRpcProvider(PROVIDER); +const wallet = new ethers.Wallet(DEPLOYER, provider); + +const trading = new ethers.Contract(tradingAddress, tradingABI, wallet); + +async function getAll(productId) { + const data = await trading.getProduct(productId); + console.log(data); +} + +async function main() { + const productId = + "0x50524f445543545f310000000000000000000000000000000000000000000000"; + + getAll(productId); +} +main(); diff --git a/scripts/workflow/mintRequest.js b/scripts/workflow/mintRequest.js new file mode 100644 index 0000000..c1d2c3d --- /dev/null +++ b/scripts/workflow/mintRequest.js @@ -0,0 +1,75 @@ +const path = require("path"); +require("dotenv").config({ path: path.join(__dirname, "..", ".env") }); + +const { + address: tradingAddress, + abi: tradingABI, +} = require("../deployments/mumbai/POP_Trading.json"); + +const { + address: usdcAddress, + abi: usdcABI, +} = require("../deployments/mumbai/MockUSDC.json"); + +const { ethers } = require("ethers"); + +const productId = ethers.utils.formatBytes32String("PRODUCT_1"); + +const PROVIDER = process.env.MUMBAI_RPC; +const DEPLOYER = process.env.PK_DEPLOYER; + +const provider = new ethers.providers.JsonRpcProvider(PROVIDER); +const wallet = new ethers.Wallet(DEPLOYER, provider); + +const trading = new ethers.Contract(tradingAddress, tradingABI, wallet); +const mock = new ethers.Contract(usdcAddress, usdcABI, wallet); + +async function request(size, strikeU, strikeL) { + try { + const txn = await trading.requestPosition( + productId, + size, + strikeL, + strikeU + ); + const receipt = await txn.wait(1); + return receipt; + } catch (err) {} +} + +async function approveTokens(fee, sz) { + try { + const size = ethers.BigNumber.from(sz); + const maxfee = ethers.BigNumber.from("1000000"); + + const protocolCut = size * fee; + const vaultCut = size * (maxfee - fee); + const toApprove = (protocolCut + vaultCut).toString(); + + const txn = await mock.approve(tradingAddress, toApprove); + const receipt = await txn.wait(1); + + return receipt; + } catch (err) {} +} + +async function getProductFee() { + const productDetails = await trading.getProduct(productId); + return productDetails.fee; +} + +async function main() { + const fee = await getProductFee(); + const sz = "10"; + + const approveSuccess = await approveTokens(fee, sz); + console.log(approveSuccess); + + const strikeU = "5"; + const strikeL = "2"; + + const requestSuccess = await request(sz, strikeU, strikeL); + console.log(requestSuccess); +} + +main(); diff --git a/scripts/workflow/mintRequestStructure.js b/scripts/workflow/mintRequestStructure.js new file mode 100644 index 0000000..d263d89 --- /dev/null +++ b/scripts/workflow/mintRequestStructure.js @@ -0,0 +1,25 @@ +const path = require("path"); +require("dotenv").config({ path: path.join(__dirname, "..", ".env") }); + +const { + address: tradingAddress, + abi: tradingABI, +} = require("../deployments/mumbai/POP_Trading.json"); + +const { ethers } = require("ethers"); + +const PROVIDER = process.env.MUMBAI_RPC; +const DEPLOYER = process.env.PK_DEPLOYER; + +const provider = new ethers.providers.JsonRpcProvider(PROVIDER); +const wallet = new ethers.Wallet(DEPLOYER, provider); + +const trading = new ethers.Contract(tradingAddress, tradingABI, wallet); + +async function getRequestIdStructure(id) { + const mintRequest = await trading.mintRequestIdToStructure(id); + console.log(mintRequest); +} + +//Min - 1 +getRequestIdStructure(1); diff --git a/scripts/workflow/mintSequencer.js b/scripts/workflow/mintSequencer.js new file mode 100644 index 0000000..0fa39c1 --- /dev/null +++ b/scripts/workflow/mintSequencer.js @@ -0,0 +1,73 @@ +const path = require("path"); +require("dotenv").config({ path: path.join(__dirname, "..", ".env") }); + +const { + address: tradingAddress, + abi: tradingABI, +} = require("../deployments/mumbai/POP_Trading.json"); + +const { ethers } = require("ethers"); + +// const productId = ethers.utils.formatBytes32String("PRODUCT_1"); + +const PROVIDER = process.env.MUMBAI_RPC; +const SEQUENCER = process.env.PK_SEQUENCER; + +const provider = new ethers.providers.JsonRpcProvider(PROVIDER); +const wallet = new ethers.Wallet(SEQUENCER, provider); + +const trading = new ethers.Contract(tradingAddress, tradingABI, wallet); + +async function getProductAndUserSpec(id) { + const mintRequest = await trading.mintRequestIdToStructure(id); + const strikeL = mintRequest["strikeLower"]; + const strikeU = mintRequest["strikeUpper"]; + + const productId = mintRequest["productId"]; + const productInfo = await trading.getProduct(productId); + const limit = productInfo.intervals; + + return [limit, strikeL, strikeU]; +} + +async function createPositionArray(limit, strikeL, strikeU) { + /// The below lambda value is mock and should be calculated based on the formulas in the yellow paper in production env. + const lambda = 43; + const positionArray = new Array(Number(limit)).fill(0); + + // For strikeL:2 and strikeU:5 we will use include 1st-4th index. + for (var i = strikeL; i <= strikeU; i++) { + positionArray[i] = lambda; + } + + return positionArray; +} + +async function execute(id, positions) { + const txn = await trading.mintPositionSequencer(id, positions, { + gasLimit: 10000000, + }); + const receipt = await txn.wait(1); + + console.log(receipt); +} + +/// Should be a valid request id. +async function main() { + try { + const requestId = 1; + + const [limit, strikeL, strikeU] = await getProductAndUserSpec(requestId); + + const positionArray = await createPositionArray(limit, strikeL, strikeU); + console.log(positionArray); + + // AFTER A POSITION IS MINTED, CHECK THE mintRequestStructure SCRIPT AND ENTER THE ID + // AND SEE THE isFullFilled IS true AND WE HAVE AN ASSOCIATED positionId. + await execute(requestId, positionArray); + } catch (err) { + console.error(err); + } +} + +main(); diff --git a/test/trading.spec.js b/test/trading.spec.js index 0f2e19e..3b2d006 100644 --- a/test/trading.spec.js +++ b/test/trading.spec.js @@ -1,6 +1,7 @@ // Import the necessary dependencies const { expect } = require("chai"); const { ethers } = require("hardhat"); +const { toWei } = require("../../transaction-service/_helpers/utils"); describe("Trading contract: ", function () { let popTrading; @@ -41,12 +42,11 @@ describe("Trading contract: ", function () { paymentToken = await DummyToken.deploy(ethers.utils.parseEther("1000000")); await paymentToken.deployed(); - const PopTrading = await ethers.getContractFactory("Trading"); + const PopTrading = await ethers.getContractFactory("POP_Trading"); popTrading = await PopTrading.deploy( paymentToken.address, sequencer.address, - stakingContract.address, - addr1.address + stakingContract.address ); await popTrading.deployed(); }); @@ -206,29 +206,43 @@ describe("Trading contract: ", function () { describe("Mint NFTs", function () { it("should allow a user to request a position", async function () { const productId = ethers.utils.formatBytes32String("PRODUCT_1"); - const size = 10; + + // Approve the Trading contract to spend payment tokens on behalf of the user + + const productAdded = await popTrading.getProduct(productId); + + const fee = productAdded.fee; + const size = "10"; + const maxfee = "1000000"; const strikeLower = 100; const strikeUpper = 200; - // Approve the Trading contract to spend payment tokens on behalf of the user - await paymentToken - .connect(user) - .approve(popTrading.address, ethers.constants.MaxUint256); + const protocolCut = size * fee; + const vaultCut = size * (maxfee - fee); + const toApprove = (protocolCut + vaultCut).toString(); + + // await mock.approve(Trading.address, toApprove); + await paymentToken.approve(popTrading.address, toApprove); - console.log("approved "); // Request a position - const trx = await popTrading - .connect(user) - .requestPosition(productId, size, strikeLower, strikeUpper); - console.log("position requestion", trx); + await popTrading.requestPosition( + productId, + size, + strikeLower, + strikeUpper + ); + // console.log("position requestion", trx); // Get the latest mint request ID - const requestId = await popTrading.nextMintRequestId(); - console.log("request id ", requestId); + let requestId = await popTrading.nextMintRequestId(); + requestId = requestId.toString() - 1; + + // console.log("request id ", requestId); // // Get the mint request associated with the request ID const mintRequest = await popTrading.mintRequestIdToStructure(requestId); + // console.log("min req", mintRequest); // Assert the mint request details - expect(mintRequest.receiver).to.equal(user.address); + expect(mintRequest.receiver).to.equal(owner.address); expect(mintRequest.productId).to.equal(productId); expect(mintRequest.size).to.equal(size); expect(mintRequest.strikeLower).to.equal(strikeLower); @@ -237,47 +251,59 @@ describe("Trading contract: ", function () { expect(mintRequest.isFullFilled).to.equal(false); }); - // it("should allow the sequencer to mint a position", async function () { - // const productId = ethers.utils.formatBytes32String("PRODUCT_1"); - // const size = 10; - // const strikeLower = 100; - // const strikeUpper = 200; + it("should allow the sequencer to mint a position", async function () { + const productId = ethers.utils.formatBytes32String("PRODUCT_1"); - // // Approve the Trading contract to spend payment tokens on behalf of the user - // await paymentToken - // .connect(user) - // .approve(popTrading.address, ethers.constants.MaxUint256); + const productAdded = await popTrading.getProduct(productId); - // // Request a position - // await popTrading - // .connect(user) - // .requestPosition(productId, size, strikeLower, strikeUpper); + const fee = productAdded.fee; + const size = "10"; + const maxfee = "1000000"; + const strikeLower = 100; + const strikeUpper = 200; - // // Get the latest mint request ID - // const requestId = await popTrading.nextMintRequestId(); + const protocolCut = size * fee; + const vaultCut = size * (maxfee - fee); + const toApprove = (protocolCut + vaultCut).toString(); - // // Get the mint request associated with the request ID - // const mintRequest = await popTrading.mintRequestIdToStructure(requestId); + // Approve the Trading contract to spend payment tokens on behalf of the user + await paymentToken.approve(popTrading.address, toApprove); - // // Sign the mint request with the sequencer address - // const sequencerSignature = await sequencer.signMessage( - // ethers.utils.arrayify(requestId) - // ); + // Request a position + await popTrading.requestPosition( + productId, + size, + strikeLower, + strikeUpper + ); - // // Mint the position as the sequencer - // await popTrading - // .connect(sequencer) - // .mintPositionSequencer(requestId, [10, 20, 30]); + // Get the latest mint request ID + let requestId = await popTrading.nextMintRequestId(); - // // Get the mint request after it has been fulfilled - // const updatedMintRequest = await popTrading.mintRequestIdToStructure( - // requestId - // ); + requestId = requestId.toString() - 1; - // // Assert the mint request has been fulfilled - // // expect(updatedMintRequest.isFullFilled).to.be.true; - // expect(updatedMintRequest.positionId).to.not.equal(0); - // }); + // Get the mint request associated with the request ID + const mintRequest = await popTrading.mintRequestIdToStructure(requestId); + + // Sign the mint request with the sequencer address + const sequencerSignature = await sequencer.signMessage( + ethers.utils.arrayify(requestId) + ); + + // Mint the position as the sequencer + await popTrading + .connect(sequencer) + .mintPositionSequencer(requestId, [10, 20, 30]); + + // Get the mint request after it has been fulfilled + const updatedMintRequest = await popTrading.mintRequestIdToStructure( + requestId + ); + + // Assert the mint request has been fulfilled + expect(updatedMintRequest.isFullFilled).to.equal(true); + expect(updatedMintRequest.positionId).to.not.equal(0); + }); // it("should allow a user to request burning a position", async function () { // const productId = ethers.utils.formatBytes32String("PRODUCT_1"); @@ -289,23 +315,24 @@ describe("Trading contract: ", function () { // const toReturnFee = ethers.utils.parseEther("0.5"); // // Approve the Trading contract to spend payment tokens on behalf of the user - // await paymentToken - // .connect(user) - // .approve(popTrading.address, ethers.constants.MaxUint256); + // await paymentToken.approve( + // popTrading.address, + // ethers.constants.MaxUint256 + // ); // // Request burning a position - // await popTrading - // .connect(user) - // .requestBurn( - // productId, - // positionId, - // sequencerSignature, - // owedFee, - // toReturnFee - // ); + // await popTrading.requestBurn( + // productId, + // positionId, + // sequencerSignature, + // owedFee, + // toReturnFee + // ); // // Get the latest burn request ID - // const requestId = await popTrading.nextBurnRequestId(); + // let requestId = await popTrading.nextBurnRequestId(); + + // requestId = requestId.toString() - 1; // // Get the burn request associated with the request ID // const burnRequest = await popTrading.burnRequestIdToStructure(requestId);