diff --git a/.env.sample b/.env.sample index 69a4b0d..77d350c 100644 --- a/.env.sample +++ b/.env.sample @@ -1,22 +1,37 @@ -# Swaplace deployed contracts addresses -SWAPLACE_ADDRESS=0xD8E3580C1b6f117c5b35DdD01dd9e50d9487501D +# Swaplace last deployed contracts address. +SWAPLACE_ADDRESS=0x000000000000000000000000000000000000000000 +# Mocks last deployed contracts addresses. +ERC20_ADDRESS=0x000000000000000000000000000000000000000000 +ERC721_ADDRESS=0x000000000000000000000000000000000000000000 +ERC1155_ADDRESS=0x000000000000000000000000000000000000000000 +# Amount of ERC20 tokens to be minted for signer address. +AMOUNT=1000 +# Token ID of ERC721 to be minted for signer address. +TOKEN_ID=1 +# The swap to be accepted by the acceptee. +SWAP_ID=1 # These are public known private keys and are here as an example. -# Funds in these accounts are at risk of being stolen. +# You should change the private keys to your own private keys. +# WARNING: Blockful advises to not, ever, use your wallet helding funds as the private keys in any kind of development. +# WARNING: funds in these account are at risk of being stolen. DEPLOYER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -# Etherscan and similar services API keys +# Etherscan and similar services API keys used to verify contract addresses in block explorers. ETHERSCAN_API_KEY=YourApiKeyToken -# EVM compatible chains and their RPC URLs -# TESTNET +# EVM compatible chains and their public RPC's +# They are not implemented in the code, but feel free to use and explore them. + +# TESTNETS GOERLI_RPC_URL=https://ethereum-goerli.publicnode.com SEPOLIA_RPC_URL=https://ethereum-sepolia.publicnode.com MUMBAI_RPC_URL=https://polygon-mumbai-bor.publicnode.com FUJI_RPC_URL=https://avalanche-fuji-c-chain.publicnode.com BNB_TESTNET_RPC_URL=https://bsc-testnet.publicnode.com +KAKAROT_SEPOLIA_RPC_URL=https://sepolia-rpc.kakarot.org -# MAINNET +# MAINNETS ETH_RPC_URL="https://eth.llamarpc.com" BNB_RPC_URL=https://binance.llamarpc.com AVAX_RPC_URL=https://avalanche-mainnet.infura.io diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0b0403f --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +## Running compile to generate the abi's and the type-chain types. +compile: + yarn compile + +## Please specify which network following the available ones in hardhat.config.ts. +network=localhost + +## Deploying Swaplace contract to the desired network. +swaplace: + yarn deploy:swaplace ${network} + +## The mocks are essential to run the tests. We will be swapping token assets. +mocks: + yarn deploy:mocks ${network} + +## Mock deployments are a requirement to run the mint script, which will fund the signer's wallet. +mint: + yarn mint ${network} + +## Mock deployments are a requirement to run the mint script, which will fund the signer's wallet. +approve: + yarn approve ${network} + +## Create a swap on the swaplace contract and save the swap id in the .env file. +swap: + yarn create-swap ${network} + +## Accept a swap on the swaplace contract based on the swap id in the .env file. +accept: + yarn accept-swap ${network} + +## Cancel a swap on the swaplace contract based on the swap id in the .env file. +cancel: + make compile + yarn cancel-swap ${network} + +## Run the entire test suite. +## Running a local node by using `npx hardhat node` in a separated terminal is +## a requirement to run the tests in the localhost network. +test-suite-runner: + yarn clean + yarn compile + make swaplace + make mocks + make mint + make approve + make swap + make accept + make swap + make cancel + +## Feed existing Swaplace contract with some transactions. +## Requires a deployed Swaplace contract and mocks. +transactions: + make mint + make approve + make swap + make accept + make swap + make cancel \ No newline at end of file diff --git a/contracts/SwapFactory.sol b/contracts/SwapFactory.sol index 5ec37fd..4e039fc 100644 --- a/contracts/SwapFactory.sol +++ b/contracts/SwapFactory.sol @@ -18,19 +18,22 @@ import {ISwapFactory} from "./interfaces/ISwapFactory.sol"; * - The `allowed` address is the address that can accept the Swap. If the allowed * address is the zero address, then anyone can accept the Swap. * - The `expiry` date is the timestamp that the Swap will be available to accept. + * - The `recipient` is the address that will receive the ETH as type uint8. If the + * recipient is equals to 0, the acceptee will receive the ETH. If the recipient is + * between 1<>255 then the recipient will be the owner of the Swap. + * - The `value` is the amount of ETH that the recipient will receive with a maximum + * of 6 decimals (0.000001 ETH). The contract will fill the value up to 18 decimals. * - The `biding` are the assets that the owner is offering. * - The `asking` are the assets that the owner wants in exchange. * * The Swap struct uses an {Asset} struct to represent the asset. This struct is * composed of: * - * - The `address` of the asset. This address can be from an ERC20 or ERC721 contract. + * - The `address` of the token asset. * - The `amount` or `id` of the asset. This amount can be the amount of ERC20 tokens - * or the ID of an ERC721 token. - * - * To use other standards, like ERC1155, you can wrap the ownership of the asset - * in an a trusted contract and Swap as an ERC721. This way, you can tokenize any - * on-chain execution and trade on Swaplace. + * or the NFT ID of an ERC721. + * - The `amount` and `id` can be encoded together in a single uint256, allowing the + * ERC1155 tokens to be swapped. */ abstract contract SwapFactory is ISwapFactory, ISwap, IErrors { /** @@ -43,39 +46,91 @@ abstract contract SwapFactory is ISwapFactory, ISwap, IErrors { return Asset(addr, amountOrId); } + /** + * @dev See {ISwapFactory-make1155Asset}. + */ + function make1155Asset( + address addr, + uint120 tokenId, + uint120 tokenAmount + ) public pure virtual returns (Asset memory) { + return Asset(addr, encodeAsset(tokenId, tokenAmount)); + } + /** * @dev See {ISwapFactory-makeSwap}. */ function makeSwap( address owner, address allowed, - uint256 expiry, + uint32 expiry, + uint8 recipient, + uint56 value, Asset[] memory biding, Asset[] memory asking ) public view virtual returns (Swap memory) { - if (expiry < block.timestamp) revert InvalidExpiry(expiry); - - if (biding.length == 0 || asking.length == 0) revert InvalidAssetsLength(); + if (expiry < block.timestamp) revert InvalidExpiry(); + uint256 config = encodeConfig(allowed, expiry, recipient, value); + return Swap(owner, config, biding, asking); + } - uint256 config = packData(allowed, expiry); + /** + * @dev See {ISwapFactory-encodeAsset}. + */ + function encodeAsset( + uint120 tokenId, + uint120 tokenAmount + ) public pure returns (uint256 amountAndId) { + return + (uint256(type(uint16).max) << 240) | + (uint256(tokenId) << 120) | + uint256(tokenAmount); + } - return Swap(owner, config, biding, asking); + /** + * @dev See {ISwapFactory-decodeAsset}. + */ + function decodeAsset( + uint256 amountOrId + ) + public + pure + returns (uint16 tokenType, uint256 tokenId, uint256 tokenAmount) + { + return ( + uint16(amountOrId >> 240), + uint256(uint120(amountOrId >> 120)), + uint256(uint120(amountOrId)) + ); } /** - * @dev See {ISwapFactory-packData}. + * @dev See {ISwapFactory-encodeConfig}. */ - function packData( + function encodeConfig( address allowed, - uint256 expiry + uint32 expiry, + uint8 recipient, + uint56 value ) public pure returns (uint256) { - return (uint256(uint160(allowed)) << 96) | uint256(expiry); + return + (uint256(uint160(allowed)) << 96) | + (uint256(expiry) << 64) | + (uint256(recipient) << 56) | + uint256(value); } /** - * @dev See {ISwapFactory-parseData}. + * @dev See {ISwapFactory-decodeConfig}. */ - function parseData(uint256 config) public pure returns (address, uint256) { - return (address(uint160(config >> 96)), uint256(config & ((1 << 96) - 1))); + function decodeConfig( + uint256 config + ) public pure returns (address, uint32, uint8, uint56) { + return ( + address(uint160(config >> 96)), + uint32(config >> 64), + uint8(config >> 56), + uint56(config) + ); } } diff --git a/contracts/Swaplace.sol b/contracts/Swaplace.sol index 901dfb5..444858e 100644 --- a/contracts/Swaplace.sol +++ b/contracts/Swaplace.sol @@ -3,15 +3,14 @@ pragma solidity ^0.8.17; import {IERC165} from "./interfaces/IERC165.sol"; import {ISwaplace} from "./interfaces/ISwaplace.sol"; -import {ITransfer} from "./interfaces/ITransfer.sol"; import {SwapFactory} from "./SwapFactory.sol"; /** * @author @0xneves | @blockful_io - * @dev Swaplace is a Decentralized Feeless DEX. It has no owners, it cannot be stopped. - * Its cern is to facilitate swaps between virtual assets following the ERC standard. + * @dev Swaplace is a decentralized and feeless DEX/OTC. Ownerless, it cannot be stopped. + * It's core is to facilitate swaps between virtual assets using the ERC standard. * Users can propose or accept swaps by allowing Swaplace to move their assets using the - * `approve` or `permit` function. + * `approve`, `permit` or similar functions. */ contract Swaplace is SwapFactory, ISwaplace, IERC165 { /// @dev Swap Identifier counter. @@ -20,21 +19,42 @@ contract Swaplace is SwapFactory, ISwaplace, IERC165 { /// @dev Mapping of Swap ID to Swap struct. See {ISwap-Swap}. mapping(uint256 => Swap) private _swaps; + /** + * @dev See {ISwaplace-getSwap}. + */ + function getSwap(uint256 swapId) public view returns (Swap memory) { + return _swaps[swapId]; + } + + /** + * @dev Getter function for _totalSwaps. + */ + function totalSwaps() public view returns (uint256) { + return _totalSwaps; + } + /** * @dev See {ISwaplace-createSwap}. */ - function createSwap(Swap calldata swap) public returns (uint256) { - if (swap.owner != msg.sender) revert InvalidAddress(msg.sender); + function createSwap(Swap calldata swap) public payable returns (uint256) { + if (swap.owner != msg.sender) revert InvalidAddress(); assembly { sstore(_totalSwaps.slot, add(sload(_totalSwaps.slot), 1)) } uint256 swapId = _totalSwaps; - _swaps[swapId] = swap; - (address allowed, ) = parseData(swap.config); + (address allowed, , uint8 recipient, uint256 value) = decodeConfig( + swap.config + ); + + if (value > 0) { + if (recipient == 0) { + if (value * 1e12 != msg.value) revert InvalidValue(); + } else if (msg.value > 0) revert InvalidValue(); + } emit SwapCreated(swapId, msg.sender, allowed); @@ -44,43 +64,31 @@ contract Swaplace is SwapFactory, ISwaplace, IERC165 { /** * @dev See {ISwaplace-acceptSwap}. */ - function acceptSwap(uint256 swapId, address receiver) public returns (bool) { + function acceptSwap( + uint256 swapId, + address receiver + ) public payable returns (bool) { Swap memory swap = _swaps[swapId]; - (address allowed, uint256 expiry) = parseData(swap.config); - - if (allowed != address(0) && allowed != msg.sender) - revert InvalidAddress(msg.sender); - - if (expiry < block.timestamp) revert InvalidExpiry(expiry); + ( + address allowed, + uint32 expiry, + uint8 recipient, + uint256 value + ) = decodeConfig(swap.config); + if (allowed != address(0) && allowed != msg.sender) revert InvalidAddress(); + if (expiry < block.timestamp) revert InvalidExpiry(); _swaps[swapId].config = 0; - Asset[] memory assets = swap.asking; + _transferFrom(msg.sender, swap.owner, swap.asking); + _transferFrom(swap.owner, receiver, swap.biding); - for (uint256 i = 0; i < assets.length; ) { - ITransfer(assets[i].addr).transferFrom( - msg.sender, - swap.owner, - assets[i].amountOrId - ); - assembly { - i := add(i, 1) - } - } - - assets = swap.biding; - - for (uint256 i = 0; i < assets.length; ) { - ITransfer(assets[i].addr).transferFrom( - swap.owner, - receiver, - assets[i].amountOrId - ); - assembly { - i := add(i, 1) - } - } + if (value > 0) + if (recipient == 0) _payNativeEth(receiver, value * 1e12); + else if (recipient > 0 && value * 1e12 == msg.value) + _payNativeEth(swap.owner, value * 1e12); + else revert InvalidValue(); emit SwapAccepted(swapId, swap.owner, msg.sender); @@ -91,22 +99,65 @@ contract Swaplace is SwapFactory, ISwaplace, IERC165 { * @dev See {ISwaplace-cancelSwap}. */ function cancelSwap(uint256 swapId) public { - if (_swaps[swapId].owner != msg.sender) revert InvalidAddress(msg.sender); - - (, uint256 expiry) = parseData(_swaps[swapId].config); + Swap memory swap = _swaps[swapId]; + if (swap.owner != msg.sender) revert InvalidAddress(); - if (expiry < block.timestamp) revert InvalidExpiry(expiry); + (, uint32 expiry, uint8 recipient, uint256 value) = decodeConfig( + swap.config + ); + if (expiry < block.timestamp && (value == 0 || recipient > 0)) { + revert InvalidExpiry(); + } _swaps[swapId].config = 0; + if (value > 0 && recipient == 0) { + _payNativeEth(msg.sender, value * 1e12); + } + emit SwapCanceled(swapId, msg.sender); } /** - * @dev See {ISwaplace-getSwap}. + * @dev Send an amount of native Ether to the receiver address. */ - function getSwap(uint256 swapId) public view returns (Swap memory) { - return _swaps[swapId]; + function _payNativeEth(address receiver, uint256 value) internal { + (bool success, ) = receiver.call{value: value}(""); + if (!success) revert InvalidValue(); + } + + /** + * @dev Transfer multiple 'assets' from 'from' to 'to'. + * + * `0x23b872dd` - Selector of the `transferFrom` function (ERC20, ERC721). + * `0xf242432a` - Selector of the `safeTransferFrom` function (ERC1155). + */ + function _transferFrom( + address from, + address to, + Asset[] memory assets + ) internal { + for (uint256 i; i < assets.length; ) { + (uint16 assetType, uint256 tokenId, uint256 tokenAmount) = decodeAsset( + assets[i].amountOrId + ); + + if (assetType == type(uint16).max) { + (bool success, ) = address(assets[i].addr).call( + abi.encodeWithSelector(0xf242432a, from, to, tokenId, tokenAmount, "") + ); + if (!success) revert InvalidCall(); + } else { + (bool success, ) = address(assets[i].addr).call( + abi.encodeWithSelector(0x23b872dd, from, to, assets[i].amountOrId) + ); + if (!success) revert InvalidCall(); + } + + assembly { + i := add(i, 1) + } + } } /** @@ -119,11 +170,4 @@ contract Swaplace is SwapFactory, ISwaplace, IERC165 { interfaceID == type(IERC165).interfaceId || interfaceID == type(ISwaplace).interfaceId; } - - /** - * @dev Getter function for _totalSwaps. - */ - function totalSwaps() public view returns (uint256) { - return _totalSwaps; - } } diff --git a/contracts/echidna/TestSwapFactory.sol b/contracts/echidna/TestSwapFactory.sol index b8ff11b..931c506 100644 --- a/contracts/echidna/TestSwapFactory.sol +++ b/contracts/echidna/TestSwapFactory.sol @@ -32,12 +32,14 @@ contract TestFactory is SwapFactory { Swap memory swap = makeSwap( owner, address(0), - block.timestamp + 1000, + uint32(block.timestamp + 1000), + 0, + 0, make_asset_array(addr, amountOrId), make_asset_array(addr, amountOrId) ); - (, uint256 expiry) = parseData(swap.config); + (, uint32 expiry, , ) = decodeConfig(swap.config); assert(expiry > block.timestamp); assert(swap.biding.length > 0); @@ -46,16 +48,14 @@ contract TestFactory is SwapFactory { } function echidna_revert_invalid_expiry() public view { - makeSwap(address(0), address(0), block.timestamp - 100, _asset, _asset); - } - - function echidna_revert_invalid_length() public view { makeSwap( address(0), address(0), - block.timestamp + 100, - new Asset[](0), - new Asset[](0) + uint32(block.timestamp - 100), + 0, + 0, + _asset, + _asset ); } } diff --git a/contracts/echidna/TestSwaplace.sol b/contracts/echidna/TestSwaplace.sol index 38d398f..d68c7f1 100644 --- a/contracts/echidna/TestSwaplace.sol +++ b/contracts/echidna/TestSwaplace.sol @@ -12,7 +12,7 @@ contract TestSwaplace is TestFactory { constructor() { _token = new MockERC20(); _swaplace = new Swaplace(); - _token.mintTo(address(this), 100); + _token.mint(address(this), 100); } function echidna_create_swap() public returns (bool) { diff --git a/contracts/interfaces/IErrors.sol b/contracts/interfaces/IErrors.sol index 25e3f99..1395042 100644 --- a/contracts/interfaces/IErrors.sol +++ b/contracts/interfaces/IErrors.sol @@ -8,20 +8,20 @@ interface IErrors { /** * @dev Displayed when the caller is not the owner of the swap. */ - error InvalidAddress(address caller); + error InvalidAddress(); /** - * @dev Displayed when the amount of {ISwap-Asset} has a length of zero. - * - * NOTE: The `biding` or `asking` array must not be empty to avoid mistakes - * when creating a swap. Assuming one side of the swap is empty, the - * correct approach should be the usage of {transferFrom} and we reinforce - * this behavior by requiring the length of the array to be bigger than zero. + * @dev Displayed when the `expiry` date is in the past. */ - error InvalidAssetsLength(); + error InvalidExpiry(); /** - * @dev Displayed when the `expiry` date is in the past. + * @dev Displayed when the `msg.value` doesn't match the swap request. + */ + error InvalidValue(); + + /** + * @dev Displayed when a low level call failed to execute. */ - error InvalidExpiry(uint256 timestamp); + error InvalidCall(); } diff --git a/contracts/interfaces/ISwap.sol b/contracts/interfaces/ISwap.sol index 693f162..c1fd5b7 100644 --- a/contracts/interfaces/ISwap.sol +++ b/contracts/interfaces/ISwap.sol @@ -23,11 +23,14 @@ interface ISwap { * @dev The Swap struct is the heart of Swaplace. * * It is composed of: - * - `owner` of the Swap. - * - `config` represents two packed values: 'allowed' for the allowed address - * to accept the swap and 'expiry' for the expiration date of the swap. - * - `biding` assets that are being bided by the owner. - * - `asking` assets that are being asked by the owner. + * - `owner` creator of the Swap. + * - `config` configuration of four packed values: + * - - `allowed` for the allowed address to accept the swap. + * - - `expiry` for the expiration date of the swap in unix time. + * - - `recipient` for the address that will receive the ETH. + * - - `value` for the amount of ETH that the recipient will receive. + * - `biding` assets offered by the swap creator. + * - `asking` assets asked by the swap creator. * * NOTE: When `allowed` address is the zero address, anyone can accept the Swap. */ diff --git a/contracts/interfaces/ISwapFactory.sol b/contracts/interfaces/ISwapFactory.sol index 70a3d43..a975ce6 100644 --- a/contracts/interfaces/ISwapFactory.sol +++ b/contracts/interfaces/ISwapFactory.sol @@ -8,42 +8,125 @@ import {ISwap} from "./ISwap.sol"; */ interface ISwapFactory { /** - * @dev Constructs an asset struct that works for ERC20 or ERC721. - * This function is a utility to easily create an `Asset` struct on-chain or off-chain. + * @dev Make an {ISwap-Asset} struct to work with token standards. + * + * @param addr is the address of the token asset. + * @param amountOrId is the amount of tokens or the ID of the NFT. */ function makeAsset( address addr, uint256 amountOrId ) external pure returns (ISwap.Asset memory); + /** + * @dev Make an {ISwap-Asset} struct to work with token standards. + * + * NOTE: Different from the {makeAsset} function, this function is used to + * encode the token ID and token amount into a single uint256. This is made + * to work with the ERC1155 standard. + * + * @param addr is the address of the token asset. + * @param tokenId is the ID of the ERC1155 token. + * @param tokenAmount is the amount of the ERC1155 token. + */ + function make1155Asset( + address addr, + uint120 tokenId, + uint120 tokenAmount + ) external pure returns (ISwap.Asset memory); + /** * @dev Build a swap struct to use in the {Swaplace-createSwap} function. * * Requirements: * - * - `expiry` cannot be in the past timestamp. - * - `biding` and `asking` cannot be empty. + * - `expiry` cannot be in the past. + * + * @param owner is the address that created the Swap. + * @param allowed is the address that can accept the Swap. If the allowed + * address is the zero address, then anyone can accept the Swap. + * @param expiry is the timestamp that the Swap will be available to accept. + * @param recipient is the address that will receive the ETH. `0` for the acceptee + * and `1<>255` for the owner. + * @param value is the amount of ETH that the recipient will receive. Maximum of + * 6 decimals (0.000001 ETH). The contract will fill the value up to 18 decimals. */ function makeSwap( address owner, address allowed, - uint256 expiry, + uint32 expiry, + uint8 recipient, + uint56 value, ISwap.Asset[] memory assets, ISwap.Asset[] memory asking ) external view returns (ISwap.Swap memory); /** - * @dev Packs `allowed` and the `expiry`. - * This function returns the bitwise packing of `allowed` and `expiry` as a uint256. + * @dev Encode `tokenId` and `tokenAmount` into a single uint256 while adding a flag + * to indicate that it's an ERC1155 token. + * + * NOTE: The flag is set to 0xFFFFFFFF. + * + * @param tokenId is the ID of the ERC1155 token. + * @param tokenAmount is the amount of the ERC1155 token. */ - function packData( + function encodeAsset( + uint120 tokenId, + uint120 tokenAmount + ) external pure returns (uint256 amountAndId); + + /** + * @dev Decode `amountOrId` returning the first 4 bytes to try match with 0xFFFFFFFF. + * If the flag is set to 0xFFFFFFFF, then it's an ERC1155 standard, otherwise it's + * assumed to be an ERC20 or ERC721. + * + * NOTE: If it's an ERC1155 token, then the next 120 bits are the token ID and the next + * 120 bits are the token amount. + * + * WARNING: Swaplace cannot handle ERC1155 tokens where the ID or the amount is greater + * than 120 bits. + * + * @param amountAndId is the amount of tokens and the ID of the ERC1155 token. + * @return tokenType is the flag to indicate the token standard. + * @return tokenId is the ID of the ERC1155 token. + * @return tokenAmount is the amount of the ERC1155 token. + * + */ + function decodeAsset( + uint256 amountAndId + ) + external + pure + returns (uint16 tokenType, uint256 tokenId, uint256 tokenAmount); + + /** + * @dev This function uses bitwise to return an encoded uint256 of the following parameters. + * + * @param allowed address is the address that can accept the Swap. If the allowed + * address is the zero address, then anyone can accept the Swap. + * @param expiry date is the timestamp that the Swap will be available to accept. + * @param recipient is the address that will receive the ETH as type uint8. If the + * recipient is equals to 0, the acceptee will receive the ETH. If the recipient is + * between 1<>255 then the recipient will be the owner of the Swap. + * @param value is the amount of ETH that the recipient will receive with a maximum + * of 6 decimals (0.000001 ETH). The contract will fill the value up to 18 decimals. + */ + function encodeConfig( address allowed, - uint256 expiry - ) external pure returns (uint256); + uint32 expiry, + uint8 recipient, + uint56 value + ) external pure returns (uint256 config); /** - * @dev Parsing the `config`. - * This function returns the extracted values of `allowed` and `expiry`. + * @dev Decode `config` into their respective variables. + * + * @param config is the encoded uint256 configuration of the Swap. */ - function parseData(uint256 config) external pure returns (address, uint256); + function decodeConfig( + uint256 config + ) + external + pure + returns (address allowed, uint32 expiry, uint8 recipient, uint56 value); } diff --git a/contracts/interfaces/ISwaplace.sol b/contracts/interfaces/ISwaplace.sol index 5dedc9d..131232d 100644 --- a/contracts/interfaces/ISwaplace.sol +++ b/contracts/interfaces/ISwaplace.sol @@ -40,8 +40,12 @@ interface ISwaplace { * - `biding` and `asking` must not be empty. * * Emits a {SwapCreated} event. + * + * @param Swap is the Swap struct to be created. */ - function createSwap(ISwap.Swap calldata Swap) external returns (uint256); + function createSwap( + ISwap.Swap calldata Swap + ) external payable returns (uint256); /** * @dev Accepts a Swap. Once the Swap is accepted, the expiry is set @@ -58,8 +62,14 @@ interface ISwaplace { * * NOTE: The expiry is set to 0, because if the Swap is expired it * will revert, preventing reentrancy attacks. + * + * @param swapId is the ID of the Swap to be accepted. + * @param receiver is the address that will receive the trading assets. */ - function acceptSwap(uint256 swapId, address receiver) external returns (bool); + function acceptSwap( + uint256 swapId, + address receiver + ) external payable returns (bool); /** * @dev Cancels an active Swap by setting the expiry to zero. @@ -73,6 +83,8 @@ interface ISwaplace { * - `expiry` must be bigger than timestamp. * * Emits a {SwapCanceled} event. + * + * @param swapId is the ID of the Swap to be canceled. */ function cancelSwap(uint256 swapId) external; @@ -82,6 +94,8 @@ interface ISwaplace { * NOTE: If the Swaps doesn't exist, the values will be defaulted to 0. * You can check if a Swap exists by checking if the `owner` is the zero address. * If the `owner` is the zero address, then the Swap doesn't exist. + * + * @param swapId is the ID of the Swap to be retrieved. */ function getSwap(uint256 swapId) external view returns (ISwap.Swap memory); } diff --git a/contracts/interfaces/ITransfer.sol b/contracts/interfaces/ITransfer.sol deleted file mode 100644 index cfde089..0000000 --- a/contracts/interfaces/ITransfer.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -/** - * @dev Generalized Interface for {IERC20} and {IERC721} `transferFrom` functions. - */ -interface ITransfer { - /** - * @dev See {IERC20-transferFrom} or {IERC721-transferFrom}. - * - * Moves an `amount` for ERC20 or `tokenId` for ERC721 from `from` to `to`. - * - * Emits a {Transfer} event. - */ - function transferFrom(address from, address to, uint256 amountOrId) external; -} diff --git a/contracts/mock/MockERC1155.sol b/contracts/mock/MockERC1155.sol new file mode 100644 index 0000000..b5904c0 --- /dev/null +++ b/contracts/mock/MockERC1155.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +contract MockERC1155 is ERC1155 { + using Strings for uint256; + + string private _name; + string private _symbol; + + function name() public view returns (string memory) { + return _name; + } + + function symbol() public view returns (string memory) { + return _symbol; + } + + constructor() + ERC1155("ipfs://QmQJnHseE9VPw5qVxuEhxTiZ7avzgkCdFz69rg86UvTZdk/") + { + _name = "MockERC1155"; + _symbol = "M1155"; + } + + function mint(address to, uint256 id, uint256 amount) public { + _mint(to, id, amount, ""); + } + + function tokenURI( + uint256 tokenId + ) public view virtual returns (string memory) { + return uri(tokenId); + } +} diff --git a/contracts/mock/MockERC20.sol b/contracts/mock/MockERC20.sol index 149f73e..af45962 100644 --- a/contracts/mock/MockERC20.sol +++ b/contracts/mock/MockERC20.sol @@ -6,7 +6,7 @@ import {ERC20} from "./ERC20/ERC20.sol"; contract MockERC20 is ERC20 { constructor() ERC20("MockERC20", "ERC20") {} - function mintTo(address to, uint256 amount) public { + function mint(address to, uint256 amount) public { _mint(to, amount); } } diff --git a/contracts/mock/MockERC721.sol b/contracts/mock/MockERC721.sol index eee2835..80bbfc8 100644 --- a/contracts/mock/MockERC721.sol +++ b/contracts/mock/MockERC721.sol @@ -8,8 +8,14 @@ contract MockERC721 is ERC721 { constructor() ERC721("MockERC721", "ERC721") {} - function mintTo(address to, uint256 id) public { + function mint(address to, uint256 id) public { totalSupply++; _mint(to, id); } -} + + function tokenURI( + uint256 + ) public view virtual override returns (string memory) { + return "ipfs://QmWodCkovJk18U75g8Veg6rCnw7951vQvTjYfS7J3nMFma/"; + } +} \ No newline at end of file diff --git a/docs/solidity-docgen/SwapFactory.md b/docs/solidity-docgen/SwapFactory.md index 2560a6d..e26aff6 100644 --- a/docs/solidity-docgen/SwapFactory.md +++ b/docs/solidity-docgen/SwapFactory.md @@ -14,19 +14,22 @@ composed of: - The `allowed` address is the address that can accept the Swap. If the allowed address is the zero address, then anyone can accept the Swap. - The `expiry` date is the timestamp that the Swap will be available to accept. +- The `recipient` is the address that will receive the ETH as type uint8. If the +recipient is equals to 0, the acceptee will receive the ETH. If the recipient is +between 1<>255 then the recipient will be the owner of the Swap. +- The `value` is the amount of ETH that the recipient will receive with a maximum +of 6 decimals (0.000001 ETH). The contract will fill the value up to 18 decimals. - The `biding` are the assets that the owner is offering. - The `asking` are the assets that the owner wants in exchange. The Swap struct uses an {Asset} struct to represent the asset. This struct is composed of: -- The `address` of the asset. This address can be from an ERC20 or ERC721 contract. +- The `address` of the token asset. - The `amount` or `id` of the asset. This amount can be the amount of ERC20 tokens - or the ID of an ERC721 token. - -To use other standards, like ERC1155, you can wrap the ownership of the asset -in an a trusted contract and Swap as an ERC721. This way, you can tokenize any -on-chain execution and trade on Swaplace._ + or the NFT ID of an ERC721. +- The `amount` and `id` can be encoded together in a single uint256, allowing the +ERC1155 tokens to be swapped._ ### makeAsset @@ -36,27 +39,51 @@ function makeAsset(address addr, uint256 amountOrId) public pure virtual returns _See {ISwapFactory-makeAsset}._ +### make1155Asset + +```solidity +function make1155Asset(address addr, uint120 tokenId, uint120 tokenAmount) public pure virtual returns (struct ISwap.Asset) +``` + +_See {ISwapFactory-make1155Asset}._ + ### makeSwap ```solidity -function makeSwap(address owner, address allowed, uint256 expiry, struct ISwap.Asset[] biding, struct ISwap.Asset[] asking) public view virtual returns (struct ISwap.Swap) +function makeSwap(address owner, address allowed, uint32 expiry, uint8 recipient, uint56 value, struct ISwap.Asset[] biding, struct ISwap.Asset[] asking) public view virtual returns (struct ISwap.Swap) ``` _See {ISwapFactory-makeSwap}._ -### packData +### encodeAsset + +```solidity +function encodeAsset(uint120 tokenId, uint120 tokenAmount) public pure returns (uint256 amountAndId) +``` + +_See {ISwapFactory-encodeAsset}._ + +### decodeAsset + +```solidity +function decodeAsset(uint256 amountOrId) public pure returns (uint16 tokenType, uint256 tokenId, uint256 tokenAmount) +``` + +_See {ISwapFactory-decodeAsset}._ + +### encodeConfig ```solidity -function packData(address allowed, uint256 expiry) public pure returns (uint256) +function encodeConfig(address allowed, uint32 expiry, uint8 recipient, uint56 value) public pure returns (uint256) ``` -_See {ISwapFactory-packData}._ +_See {ISwapFactory-encodeConfig}._ -### parseData +### decodeConfig ```solidity -function parseData(uint256 config) public pure returns (address, uint256) +function decodeConfig(uint256 config) public pure returns (address, uint32, uint8, uint56) ``` -_See {ISwapFactory-parseData}._ +_See {ISwapFactory-decodeConfig}._ diff --git a/docs/solidity-docgen/Swaplace.md b/docs/solidity-docgen/Swaplace.md index 1a76ad9..5025de7 100644 --- a/docs/solidity-docgen/Swaplace.md +++ b/docs/solidity-docgen/Swaplace.md @@ -2,15 +2,31 @@ ## Swaplace -_Swaplace is a Decentralized Feeless DEX. It has no owners, it cannot be stopped. -Its cern is to facilitate swaps between virtual assets following the ERC standard. +_Swaplace is a decentralized and feeless DEX/OTC. Ownerless, it cannot be stopped. +It's core is to facilitate swaps between virtual assets using the ERC standard. Users can propose or accept swaps by allowing Swaplace to move their assets using the -`approve` or `permit` function._ +`approve`, `permit` or similar functions._ + +### getSwap + +```solidity +function getSwap(uint256 swapId) public view returns (struct ISwap.Swap) +``` + +_See {ISwaplace-getSwap}._ + +### totalSwaps + +```solidity +function totalSwaps() public view returns (uint256) +``` + +_Getter function for _totalSwaps._ ### createSwap ```solidity -function createSwap(struct ISwap.Swap swap) public returns (uint256) +function createSwap(struct ISwap.Swap swap) public payable returns (uint256) ``` _See {ISwaplace-createSwap}._ @@ -18,7 +34,7 @@ _See {ISwaplace-createSwap}._ ### acceptSwap ```solidity -function acceptSwap(uint256 swapId, address receiver) public returns (bool) +function acceptSwap(uint256 swapId, address receiver) public payable returns (bool) ``` _See {ISwaplace-acceptSwap}._ @@ -31,27 +47,30 @@ function cancelSwap(uint256 swapId) public _See {ISwaplace-cancelSwap}._ -### getSwap +### _payNativeEth ```solidity -function getSwap(uint256 swapId) public view returns (struct ISwap.Swap) +function _payNativeEth(address receiver, uint256 value) internal ``` -_See {ISwaplace-getSwap}._ +_Send an amount of native Ether to the receiver address._ -### supportsInterface +### _transferFrom ```solidity -function supportsInterface(bytes4 interfaceID) external pure returns (bool) +function _transferFrom(address from, address to, struct ISwap.Asset[] assets) internal ``` -_See {IERC165-supportsInterface}._ +_Transfer multiple 'assets' from 'from' to 'to'. -### totalSwaps +`0x23b872dd` - Selector of the `transferFrom` function (ERC20, ERC721). +`0xf242432a` - Selector of the `safeTransferFrom` function (ERC1155)._ + +### supportsInterface ```solidity -function totalSwaps() public view returns (uint256) +function supportsInterface(bytes4 interfaceID) external pure returns (bool) ``` -_Getter function for _totalSwaps._ +_See {IERC165-supportsInterface}._ diff --git a/docs/solidity-docgen/echidna/TestSwapFactory.md b/docs/solidity-docgen/echidna/TestSwapFactory.md deleted file mode 100644 index 19a47e6..0000000 --- a/docs/solidity-docgen/echidna/TestSwapFactory.md +++ /dev/null @@ -1,34 +0,0 @@ -# Solidity API - -## TestFactory - -### has_values - -```solidity -function has_values() public -``` - -### make_asset_array - -```solidity -function make_asset_array(address addr, uint256 amountOrId) public pure returns (struct ISwap.Asset[]) -``` - -### make_valid_swap - -```solidity -function make_valid_swap(address owner, address addr, uint256 amountOrId) public view returns (struct ISwap.Swap) -``` - -### echidna_revert_invalid_expiry - -```solidity -function echidna_revert_invalid_expiry() public view -``` - -### echidna_revert_invalid_length - -```solidity -function echidna_revert_invalid_length() public view -``` - diff --git a/docs/solidity-docgen/echidna/TestSwaplace.md b/docs/solidity-docgen/echidna/TestSwaplace.md deleted file mode 100644 index 322734e..0000000 --- a/docs/solidity-docgen/echidna/TestSwaplace.md +++ /dev/null @@ -1,34 +0,0 @@ -# Solidity API - -## TestSwaplace - -### constructor - -```solidity -constructor() public -``` - -### echidna_create_swap - -```solidity -function echidna_create_swap() public returns (bool) -``` - -### echidna_accept_swap - -```solidity -function echidna_accept_swap() public returns (bool) -``` - -### echidna_id_overflow - -```solidity -function echidna_id_overflow() public view returns (bool) -``` - -### echidna_id_never_zero_after_init - -```solidity -function echidna_id_never_zero_after_init() public view returns (bool) -``` - diff --git a/docs/solidity-docgen/interfaces/IErrors.md b/docs/solidity-docgen/interfaces/IErrors.md index b30555a..4b1b1bc 100644 --- a/docs/solidity-docgen/interfaces/IErrors.md +++ b/docs/solidity-docgen/interfaces/IErrors.md @@ -7,29 +7,32 @@ _Errors only interface for the {Swaplace} implementations._ ### InvalidAddress ```solidity -error InvalidAddress(address caller) +error InvalidAddress() ``` _Displayed when the caller is not the owner of the swap._ -### InvalidAssetsLength +### InvalidExpiry ```solidity -error InvalidAssetsLength() +error InvalidExpiry() ``` -_Displayed when the amount of {ISwap-Asset} has a length of zero. +_Displayed when the `expiry` date is in the past._ -NOTE: The `biding` or `asking` array must not be empty to avoid mistakes -when creating a swap. Assuming one side of the swap is empty, the -correct approach should be the usage of {transferFrom} and we reinforce -this behavior by requiring the length of the array to be bigger than zero._ +### InvalidValue -### InvalidExpiry +```solidity +error InvalidValue() +``` + +_Displayed when the `msg.value` doesn't match the swap request._ + +### InvalidCall ```solidity -error InvalidExpiry(uint256 timestamp) +error InvalidCall() ``` -_Displayed when the `expiry` date is in the past._ +_Displayed when a low level call failed to execute._ diff --git a/docs/solidity-docgen/interfaces/ISwapFactory.md b/docs/solidity-docgen/interfaces/ISwapFactory.md index e90f35e..076fe9a 100644 --- a/docs/solidity-docgen/interfaces/ISwapFactory.md +++ b/docs/solidity-docgen/interfaces/ISwapFactory.md @@ -10,37 +10,135 @@ _Interface of the {SwapFactory} implementation._ function makeAsset(address addr, uint256 amountOrId) external pure returns (struct ISwap.Asset) ``` -_Constructs an asset struct that works for ERC20 or ERC721. -This function is a utility to easily create an `Asset` struct on-chain or off-chain._ +_Make an {ISwap-Asset} struct to work with token standards._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| addr | address | is the address of the token asset. | +| amountOrId | uint256 | is the amount of tokens or the ID of the NFT. | + +### make1155Asset + +```solidity +function make1155Asset(address addr, uint120 tokenId, uint120 tokenAmount) external pure returns (struct ISwap.Asset) +``` + +_Make an {ISwap-Asset} struct to work with token standards. + +NOTE: Different from the {makeAsset} function, this function is used to +encode the token ID and token amount into a single uint256. This is made +to work with the ERC1155 standard._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| addr | address | is the address of the token asset. | +| tokenId | uint120 | is the ID of the ERC1155 token. | +| tokenAmount | uint120 | is the amount of the ERC1155 token. | ### makeSwap ```solidity -function makeSwap(address owner, address allowed, uint256 expiry, struct ISwap.Asset[] assets, struct ISwap.Asset[] asking) external view returns (struct ISwap.Swap) +function makeSwap(address owner, address allowed, uint32 expiry, uint8 recipient, uint56 value, struct ISwap.Asset[] assets, struct ISwap.Asset[] asking) external view returns (struct ISwap.Swap) ``` _Build a swap struct to use in the {Swaplace-createSwap} function. Requirements: -- `expiry` cannot be in the past timestamp. -- `biding` and `asking` cannot be empty._ +- `expiry` cannot be in the past._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| owner | address | is the address that created the Swap. | +| allowed | address | is the address that can accept the Swap. If the allowed address is the zero address, then anyone can accept the Swap. | +| expiry | uint32 | is the timestamp that the Swap will be available to accept. | +| recipient | uint8 | is the address that will receive the ETH. `0` for the acceptee and `1<>255` for the owner. | +| value | uint56 | is the amount of ETH that the recipient will receive. Maximum of 6 decimals (0.000001 ETH). The contract will fill the value up to 18 decimals. | +| assets | struct ISwap.Asset[] | | +| asking | struct ISwap.Asset[] | | -### packData +### encodeAsset ```solidity -function packData(address allowed, uint256 expiry) external pure returns (uint256) +function encodeAsset(uint120 tokenId, uint120 tokenAmount) external pure returns (uint256 amountAndId) ``` -_Packs `allowed` and the `expiry`. -This function returns the bitwise packing of `allowed` and `expiry` as a uint256._ +_Encode `tokenId` and `tokenAmount` into a single uint256 while adding a flag +to indicate that it's an ERC1155 token. + +NOTE: The flag is set to 0xFFFFFFFF._ + +#### Parameters -### parseData +| Name | Type | Description | +| ---- | ---- | ----------- | +| tokenId | uint120 | is the ID of the ERC1155 token. | +| tokenAmount | uint120 | is the amount of the ERC1155 token. | + +### decodeAsset ```solidity -function parseData(uint256 config) external pure returns (address, uint256) +function decodeAsset(uint256 amountAndId) external pure returns (uint16 tokenType, uint256 tokenId, uint256 tokenAmount) ``` -_Parsing the `config`. -This function returns the extracted values of `allowed` and `expiry`._ +_Decode `amountOrId` returning the first 4 bytes to try match with 0xFFFFFFFF. +If the flag is set to 0xFFFFFFFF, then it's an ERC1155 standard, otherwise it's +assumed to be an ERC20 or ERC721. + +NOTE: If it's an ERC1155 token, then the next 120 bits are the token ID and the next +120 bits are the token amount. + +WARNING: Swaplace cannot handle ERC1155 tokens where the ID or the amount is greater +than 120 bits._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| amountAndId | uint256 | is the amount of tokens and the ID of the ERC1155 token. | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| tokenType | uint16 | is the flag to indicate the token standard. | +| tokenId | uint256 | is the ID of the ERC1155 token. | +| tokenAmount | uint256 | is the amount of the ERC1155 token. | + +### encodeConfig + +```solidity +function encodeConfig(address allowed, uint32 expiry, uint8 recipient, uint56 value) external pure returns (uint256 config) +``` + +_This function uses bitwise to return an encoded uint256 of the following parameters._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| allowed | address | address is the address that can accept the Swap. If the allowed address is the zero address, then anyone can accept the Swap. | +| expiry | uint32 | date is the timestamp that the Swap will be available to accept. | +| recipient | uint8 | is the address that will receive the ETH as type uint8. If the recipient is equals to 0, the acceptee will receive the ETH. If the recipient is between 1<>255 then the recipient will be the owner of the Swap. | +| value | uint56 | is the amount of ETH that the recipient will receive with a maximum of 6 decimals (0.000001 ETH). The contract will fill the value up to 18 decimals. | + +### decodeConfig + +```solidity +function decodeConfig(uint256 config) external pure returns (address allowed, uint32 expiry, uint8 recipient, uint56 value) +``` + +_Decode `config` into their respective variables._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| config | uint256 | is the encoded uint256 configuration of the Swap. | diff --git a/docs/solidity-docgen/interfaces/ISwaplace.md b/docs/solidity-docgen/interfaces/ISwaplace.md index 04846c2..8356ef4 100644 --- a/docs/solidity-docgen/interfaces/ISwaplace.md +++ b/docs/solidity-docgen/interfaces/ISwaplace.md @@ -31,7 +31,7 @@ _Emitted when a Swap is canceled._ ### createSwap ```solidity -function createSwap(struct ISwap.Swap Swap) external returns (uint256) +function createSwap(struct ISwap.Swap Swap) external payable returns (uint256) ``` _Allow users to create a Swap. Each new Swap self-increments its ID by one. @@ -44,10 +44,16 @@ Requirements: Emits a {SwapCreated} event._ +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| Swap | struct ISwap.Swap | is the Swap struct to be created. | + ### acceptSwap ```solidity -function acceptSwap(uint256 swapId, address receiver) external returns (bool) +function acceptSwap(uint256 swapId, address receiver) external payable returns (bool) ``` _Accepts a Swap. Once the Swap is accepted, the expiry is set @@ -65,6 +71,13 @@ Emits a {SwapAccepted} event. NOTE: The expiry is set to 0, because if the Swap is expired it will revert, preventing reentrancy attacks._ +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| swapId | uint256 | is the ID of the Swap to be accepted. | +| receiver | address | is the address that will receive the trading assets. | + ### cancelSwap ```solidity @@ -83,6 +96,12 @@ Requirements: Emits a {SwapCanceled} event._ +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| swapId | uint256 | is the ID of the Swap to be canceled. | + ### getSwap ```solidity @@ -95,3 +114,9 @@ NOTE: If the Swaps doesn't exist, the values will be defaulted to 0. You can check if a Swap exists by checking if the `owner` is the zero address. If the `owner` is the zero address, then the Swap doesn't exist._ +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| swapId | uint256 | is the ID of the Swap to be retrieved. | + diff --git a/docs/solidity-docgen/interfaces/ITransfer.md b/docs/solidity-docgen/interfaces/ITransfer.md deleted file mode 100644 index b9e50de..0000000 --- a/docs/solidity-docgen/interfaces/ITransfer.md +++ /dev/null @@ -1,18 +0,0 @@ -# Solidity API - -## ITransfer - -_Generalized Interface for {IERC20} and {IERC721} `transferFrom` functions._ - -### transferFrom - -```solidity -function transferFrom(address from, address to, uint256 amountOrId) external -``` - -_See {IERC20-transferFrom} or {IERC721-transferFrom}. - -Moves an `amount` for ERC20 or `tokenId` for ERC721 from `from` to `to`. - -Emits a {Transfer} event._ - diff --git a/docs/solidity-docgen/mock/ERC20/ERC20.md b/docs/solidity-docgen/mock/ERC20/ERC20.md deleted file mode 100644 index 1075c96..0000000 --- a/docs/solidity-docgen/mock/ERC20/ERC20.md +++ /dev/null @@ -1,205 +0,0 @@ -# Solidity API - -## ERC20 - -_Lightweight ERC20 with Permit extension._ - -### constructor - -```solidity -constructor(string name_, string symbol_) internal -``` - -_Sets the values for {name} and {symbol}._ - -### name - -```solidity -function name() public view virtual returns (string) -``` - -_Returns the name of the token._ - -### symbol - -```solidity -function symbol() public view virtual returns (string) -``` - -_Returns the symbol of the token, usually a shorter version of the -name._ - -### decimals - -```solidity -function decimals() public view virtual returns (uint8) -``` - -_Returns the number of decimals used to get its user representation. -For example, if `decimals` equals `2`, a balance of `505` tokens should -be displayed to a user as `5.05` (`505 / 10 ** 2`). - -Tokens usually opt for a value of 18, imitating the relationship between -Ether and Wei. This is the default value returned by this function, unless -it's overridden. - -NOTE: This information is only used for _display_ purposes: it in -no way affects any of the arithmetic of the contract, including -{IERC20-balanceOf} and {IERC20-transfer}._ - -### totalSupply - -```solidity -function totalSupply() public view virtual returns (uint256) -``` - -_See {IERC20-totalSupply}._ - -### balanceOf - -```solidity -function balanceOf(address account) public view virtual returns (uint256) -``` - -_See {IERC20-balanceOf}._ - -### allowance - -```solidity -function allowance(address owner, address spender) public view virtual returns (uint256) -``` - -_See {IERC20-allowance}._ - -### nonces - -```solidity -function nonces(address owner) public view virtual returns (uint256) -``` - -_Returns the current nonce of an address._ - -### DOMAIN_SEPARATOR - -```solidity -function DOMAIN_SEPARATOR() public view virtual returns (bytes32) -``` - -_See {IERC20Permit-DOMAIN_SEPARATOR}._ - -### approve - -```solidity -function approve(address spender, uint256 value) public virtual returns (bool) -``` - -_See {IERC20-approve}. - -NOTE: If `value` is the maximum `uint256`, the allowance is not updated on -`transferFrom`. This is semantically equivalent to an infinite approval._ - -### increaseAllowance - -```solidity -function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) -``` - -_See {IERC20-increaseAllowance}._ - -### decreaseAllowance - -```solidity -function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) -``` - -_See {IERC20-decreaseAllowance}. - -Requirements: - -- `spender` must have allowance for the caller of at least -`requestedDecrease`._ - -### permit - -```solidity -function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual returns (bool) -``` - -_See {IERC20Permit-permit}. - -Requirements: - -- `spender` cannot be the zero address. -- `deadline` must be a timestamp in the future. -- `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` -over the EIP712-formatted function arguments. -- the signature must use ``owner``'s current nonce (see {IERC20Permit-nonces})._ - -### _mint - -```solidity -function _mint(address to, uint256 value) internal -``` - -_Creates an `value` of tokens and assigns them to `to` by creating supply. - -Emits a {Transfer} event with `from` set to the zero address._ - -### _burn - -```solidity -function _burn(address from, uint256 value) internal -``` - -_Destroys an `value` of tokens from `from` by lowering the total supply. - -Requirements: - -- `from` must have a balance of at least `value`. - -Emits a {Transfer} event with `to` set to the zero address._ - -### transfer - -```solidity -function transfer(address to, uint256 value) public virtual returns (bool) -``` - -_See {IERC20-transfer}. - -Requirements: - -- the caller must have a balance of at least `value`._ - -### transferFrom - -```solidity -function transferFrom(address from, address to, uint256 value) public virtual returns (bool) -``` - -_See {IERC20-transferFrom}. - -Requirements: - -- `from` must have a balance of at least `value`. -- the caller must have allowance for `from`'s tokens of at least -`value`. - -NOTE: Does not update the allowance if the current allowance -is the maximum `uint256`._ - -### permitTransfer - -```solidity -function permitTransfer(address from, address to, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual returns (bool) -``` - -_See {IERC20Permit-permitTransfer}. - -Requirements: - -- `deadline` must be a timestamp in the future. -- `v`, `r` and `s` must be a valid `secp256k1` signature from `from` -over the EIP712-formatted function arguments. -- the signature must use `from`'s current nonce (see {IERC20Permit-nonces})._ - diff --git a/docs/solidity-docgen/mock/ERC20/interfaces/IERC20.md b/docs/solidity-docgen/mock/ERC20/interfaces/IERC20.md deleted file mode 100644 index d5bf51b..0000000 --- a/docs/solidity-docgen/mock/ERC20/interfaces/IERC20.md +++ /dev/null @@ -1,154 +0,0 @@ -# Solidity API - -## IERC20 - -_Interface of the ERC20 standard as defined in the EIP._ - -### Approval - -```solidity -event Approval(address owner, address spender, uint256 value) -``` - -_Emitted when the allowance of a `spender` for an `owner` is set by -a call to {approve}. `value` is the new allowance._ - -### Transfer - -```solidity -event Transfer(address from, address to, uint256 value) -``` - -_Emitted when `value` tokens are moved from `from` to `to`. - -NOTE: `value` can be zero._ - -### name - -```solidity -function name() external view returns (string) -``` - -_Returns the name of the token._ - -### symbol - -```solidity -function symbol() external view returns (string) -``` - -_Returns the symbol of the token._ - -### decimals - -```solidity -function decimals() external view returns (uint8) -``` - -_Returns the decimals places of the token._ - -### totalSupply - -```solidity -function totalSupply() external view returns (uint256) -``` - -_Returns the value of tokens in existence._ - -### balanceOf - -```solidity -function balanceOf(address account) external view returns (uint256) -``` - -_Returns the value of tokens owned by `account`._ - -### allowance - -```solidity -function allowance(address owner, address spender) external view returns (uint256) -``` - -_Returns the remaining number of tokens that `spender` will be -allowed to spend on behalf of `owner` through {transferFrom}. This is -zero by default. - -This value changes when {approve} or {transferFrom} are called. - -NOTE: If `value` is the maximum `uint256`, the allowance is not updated on -`transferFrom`. This is semantically equivalent to an infinite approval._ - -### approve - -```solidity -function approve(address spender, uint256 value) external returns (bool) -``` - -_Sets a `value` amount of tokens as the allowance of `spender` over the -caller's tokens. - -Returns a boolean value indicating whether the operation succeeded. - -IMPORTANT: Beware that changing an allowance with this method brings the risk -that someone may use both the old and the new allowance by unfortunate -transaction ordering. One possible solution to mitigate this race -condition is to first reduce the spender's allowance to 0 and set the -desired value afterwards: -https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - -Emits an {Approval} event._ - -### increaseAllowance - -```solidity -function increaseAllowance(address spender, uint256 addedValue) external returns (bool) -``` - -_Atomically increases the allowance granted to `spender` by the caller. - -This is an alternative to {approve} that can be used as a mitigation for -problems described in {IERC20-approve}. - -Emits an {IERC20-Approval} event indicating the updated allowance._ - -### decreaseAllowance - -```solidity -function decreaseAllowance(address spender, uint256 requestedDecrease) external returns (bool) -``` - -_Atomically decreases the allowance granted to `spender` by the caller. - -This is an alternative to {approve} that can be used as a mitigation for -problems described in {IERC20-approve}. - -Emits an {Approval} event indicating the updated allowance. - -NOTE: Although this function is designed to avoid double spending with {approval}, -it can still be frontrunned, preventing any attempt of allowance reduction._ - -### transfer - -```solidity -function transfer(address to, uint256 value) external returns (bool) -``` - -_Moves a `value` amount of tokens from the caller's account to `to`. -Returns a boolean value indicating whether the operation succeeded. - -Emits a {Transfer} event._ - -### transferFrom - -```solidity -function transferFrom(address from, address to, uint256 value) external returns (bool) -``` - -_Moves a `value` amount of tokens from `from` to `to` using the -allowance mechanism. `value` is then deducted from the caller's -allowance. - -Returns a boolean value indicating whether the operation succeeded. - -Emits a {Transfer} event._ - diff --git a/docs/solidity-docgen/mock/ERC20/interfaces/IERC20Errors.md b/docs/solidity-docgen/mock/ERC20/interfaces/IERC20Errors.md deleted file mode 100644 index b0f5ae6..0000000 --- a/docs/solidity-docgen/mock/ERC20/interfaces/IERC20Errors.md +++ /dev/null @@ -1,98 +0,0 @@ -# Solidity API - -## IERC20Errors - -_Standard ERC20 Errors_ - -### ERC20InsufficientBalance - -```solidity -error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed) -``` - -_Indicates an error related to the current `balance` of a `sender`. Used in transfers._ - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| sender | address | Address whose tokens are being transferred. | -| balance | uint256 | Current balance for the interacting account. | -| needed | uint256 | Minimum amount required to perform a transfer. | - -### ERC20InsufficientAllowance - -```solidity -error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed) -``` - -_Indicates a failure with the `spender`’s `allowance`. Used in transfers._ - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| spender | address | Address that may be allowed to operate on tokens without being their owner. | -| allowance | uint256 | Amount of tokens a `spender` is allowed to operate with. | -| needed | uint256 | Minimum amount required to perform a transfer. | - -### ERC20FailedDecreaseAllowance - -```solidity -error ERC20FailedDecreaseAllowance(address spender, uint256 allowance, uint256 needed) -``` - -_Indicates a failed `decreaseAllowance` request._ - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| spender | address | Address that may be allowed to operate on tokens without being their owner. | -| allowance | uint256 | Amount of tokens a `spender` want to operate with. | -| needed | uint256 | Amount required to decrease the allowance. | - -### ERC20PermitInvalidNonce - -```solidity -error ERC20PermitInvalidNonce(address account, uint256 nonce) -``` - -_Indicates the nonce used for an `account` is not the expected current nonce._ - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| account | address | Address whose nonce is being checked. | -| nonce | uint256 | Expected nonce for the given `account`. | - -### ERC2612ExpiredSignature - -```solidity -error ERC2612ExpiredSignature(uint256 deadline) -``` - -_Indicates the expiration of a permit to be used._ - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| deadline | uint256 | Expiration time limit in seconds. | - -### ERC2612InvalidSigner - -```solidity -error ERC2612InvalidSigner(address signer, address owner) -``` - -_Indicates the mismatched owner when validating the signature._ - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| signer | address | Address of the signer recovered. | -| owner | address | Address of the owner expected to match `signer`. | - diff --git a/docs/solidity-docgen/mock/ERC20/interfaces/IERC20Permit.md b/docs/solidity-docgen/mock/ERC20/interfaces/IERC20Permit.md deleted file mode 100644 index bf13c88..0000000 --- a/docs/solidity-docgen/mock/ERC20/interfaces/IERC20Permit.md +++ /dev/null @@ -1,69 +0,0 @@ -# Solidity API - -## IERC20Permit - -_Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in -https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. - -Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by -presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't -need to send a transaction, and thus is not required to hold Ether at all._ - -### nonces - -```solidity -function nonces(address owner) external view returns (uint256) -``` - -_Returns the current nonce for `owner`. This value must be -included whenever a signature is generated for {permit}. - -Every successful call to {permit} increases `owner`'s nonce by one. -This prevents a signature from being used multiple times._ - -### DOMAIN_SEPARATOR - -```solidity -function DOMAIN_SEPARATOR() external view returns (bytes32) -``` - -_Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}._ - -### permit - -```solidity -function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external returns (bool) -``` - -_Sets `value` as the allowance of `spender` over `owner`'s tokens, -given `owner`'s signed approval. - -IMPORTANT: The same issues {IERC20-approve} has related to transaction -ordering also apply here. - -Emits an {IERC20-Approval} event. - -NOTE: `spender` can be the zero address. Checking this on-chain is a bad -usage of gas. For more information on the signature format, see the -https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIPsection]._ - -### permitTransfer - -```solidity -function permitTransfer(address from, address to, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external returns (bool) -``` - -_Allows {IERC20-transferFrom} to be used with the `owner`'s signature. -Similar to permit but changing the scope to handle the balance instead of -allowance. - -Requires less gas than regular {permit} and {IERC20-transferFrom}. - -IMPORTANT: `owner` works as `from` and `spender` as `to` (see {IERC20Permit-permit}). - -Emits an {IERC20-Transfer} event. - -NOTE: Realize that {PERMIT_TYPEHASH} is different from the one in {permit}. -This is because the arguments name differ. But won't result in a different -output as long as it is encoded following the EIP712 and ERC20Permit specs._ - diff --git a/docs/solidity-docgen/mock/MockERC20.md b/docs/solidity-docgen/mock/MockERC20.md deleted file mode 100644 index ee80b9c..0000000 --- a/docs/solidity-docgen/mock/MockERC20.md +++ /dev/null @@ -1,16 +0,0 @@ -# Solidity API - -## MockERC20 - -### constructor - -```solidity -constructor() public -``` - -### mintTo - -```solidity -function mintTo(address to, uint256 amount) public -``` - diff --git a/docs/solidity-docgen/mock/MockERC721.md b/docs/solidity-docgen/mock/MockERC721.md deleted file mode 100644 index ad0f209..0000000 --- a/docs/solidity-docgen/mock/MockERC721.md +++ /dev/null @@ -1,22 +0,0 @@ -# Solidity API - -## MockERC721 - -### totalSupply - -```solidity -uint256 totalSupply -``` - -### constructor - -```solidity -constructor() public -``` - -### mintTo - -```solidity -function mintTo(address to, uint256 id) public -``` - diff --git a/hardhat.config.ts b/hardhat.config.ts index 2c5ee05..c18e639 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -4,11 +4,21 @@ import "solidity-docgen"; import dotenv from "dotenv"; dotenv.config(); +// Using a hardcoded solution to avoid GitHub actions issues const DEPLOYER_PRIVATE_KEY = - "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + process.env.DEPLOYER_PRIVATE_KEY || + ""; const config: HardhatUserConfig = { - solidity: "0.8.17", + solidity: { + version: "0.8.17", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, etherscan: { apiKey: `${process.env.ETHERSCAN_API_KEY}`, }, @@ -16,32 +26,38 @@ const config: HardhatUserConfig = { /** * @dev Testnets */ + kakarot: { + url: `${process.env.KAKAROT_SEPOLIA_RPC_URL}`, + accounts: [`${DEPLOYER_PRIVATE_KEY}`], + }, sepolia: { url: `${process.env.SEPOLIA_RPC_URL}`, - accounts: [ - `${ - process.env.DEPLOYER_PRIVATE_KEY - ? process.env.DEPLOYER_PRIVATE_KEY - : DEPLOYER_PRIVATE_KEY - }`, - ], + accounts: [`${DEPLOYER_PRIVATE_KEY}`], }, - goerli: { - url: `${process.env.SEPOLIA_RPC_URL}`, + amoy: { + url: `${process.env.AMOY_RPC_URL}`, accounts: [`${DEPLOYER_PRIVATE_KEY}`], }, - mumbai: { - url: `${process.env.MUMBAI_RPC_URL}`, + opsepolia: { + url: `${process.env.OPSEPOLIA_RPC_URL}`, accounts: [`${DEPLOYER_PRIVATE_KEY}`], }, fuji: { url: `${process.env.FUJI_RPC_URL}`, accounts: [`${DEPLOYER_PRIVATE_KEY}`], }, - bnbtest: { + bnb_testnet: { url: `${process.env.BNB_TESTNET_RPC_URL}`, accounts: [`${DEPLOYER_PRIVATE_KEY}`], }, + arbitrum_sepolia: { + url: `${process.env.ARBITRUM_SEPOLIA_RPC_URL}`, + accounts: [`${DEPLOYER_PRIVATE_KEY}`], + }, + base_sepolia: { + url: `${process.env.BASE_SEPOLIA_RPC_URL}`, + accounts: [`${DEPLOYER_PRIVATE_KEY}`], + }, /** * @dev Mainnets */ @@ -53,17 +69,11 @@ const config: HardhatUserConfig = { url: `${process.env.MATIC_RPC_URL}`, accounts: [`${DEPLOYER_PRIVATE_KEY}`], }, - avalanche: { - url: `${process.env.AVAX_RPC_URL}`, - accounts: [`${DEPLOYER_PRIVATE_KEY}`], - }, - binance: { - url: `${process.env.BNB_RPC_URL}`, - accounts: [`${DEPLOYER_PRIVATE_KEY}`], - }, - fantom: { - url: `${process.env.FTM_RPC_URL}`, - accounts: [`${DEPLOYER_PRIVATE_KEY}`], + /** + * @dev Localnet (Hardhat) + */ + hardhat: { + chainId: 31337, }, }, defaultNetwork: "hardhat", @@ -74,6 +84,7 @@ const config: HardhatUserConfig = { gasReporter: { enabled: true, }, + allowUnlimitedContractSize: true, }; export default config; diff --git a/package.json b/package.json index 60b3a11..b904e4b 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,13 @@ "scripts": { "clean": "npx hardhat clean", "compile": "npx hardhat compile", - "test": "npx hardhat test", "docs": "npx hardhat docgen", + "test": "npx hardhat test", + "mint": "npx hardhat run scripts/mint.ts --network $1", + "create-swap": "npx hardhat run scripts/createSwap.ts --network $1", + "accept-swap": "npx hardhat run scripts/acceptSwap.ts --network $1", + "cancel-swap": "npx hardhat run scripts/cancelSwap.ts --network $1", + "approve": "npx hardhat run scripts/approve.ts --network $1", "deploy:mocks": "npx hardhat run scripts/deployMock.ts --network $1", "deploy:swaplace": "npx hardhat run scripts/deploy.ts --network $1", "compile-echidna": "crytic-compile . && slither . --print echidna", diff --git a/scripts/acceptSwap.ts b/scripts/acceptSwap.ts index 798c382..1ae0430 100644 --- a/scripts/acceptSwap.ts +++ b/scripts/acceptSwap.ts @@ -1,27 +1,80 @@ import { ethers } from "hardhat"; -import abi from "../artifacts/contracts/Swaplace.sol/Swaplace.json"; +import { Contract } from "ethers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; export async function main() { - // Get the first account from the list of accounts - const [signer, receiver] = await ethers.getSigners(); + /// @dev The Swaplace address also needs to be instance to receive the approvals. + const SWAPLACE_ADDRESS = process.env.SWAPLACE_ADDRESS || 0x0; + /// @dev Will throw an error if any of the addresses were not set in the `.env` file. + if (!SWAPLACE_ADDRESS) { + throw new Error( + "Invalid Swaplace address, please check if the addresse in the `.env` file is set up correctly.", + ); + } - // Get the Swaplace address from .env file - const swaplaceAddress: string = process.env.SWAPLACE_ADDRESS || ""; + /// @dev The swap to be approved + let swapId = process.env.SWAP_ID || 0x0; - // Get the Swaplace instance - const Swaplace = new ethers.Contract(swaplaceAddress, abi.abi, signer); + /// @dev No point in approving if the quantities and target token IDs are not set. + if (!swapId) { + throw new Error( + "Invalid swap ID, please check if the values are set up correctly in the `.env` file.", + ); + } - // Get the swap ID to be accepted - const swapId = 1; + /// @dev This is the list of accounts that were set in the hardhat config file. + /// The first account will be performing the signing of the transactions, hence becoming the contract deployer. + let signers: SignerWithAddress[]; - // Accept the swap - const tx = await Swaplace.acceptSwap(swapId, receiver.address); + /// @dev The returned contract instance that will be deployed via the deploy function in utils. + let Swaplace: Contract; - // Wait for the transaction to be mined - await tx.wait(); + /// @dev will throw an error if any of the accounts was not set up correctly. + try { + signers = await ethers.getSigners(); + } catch (error) { + throw new Error( + `Error getting the first account from the list of accounts. Make sure it is + set up in correctly in hardhat.config.ts. + ${error}`, + ); + } - // Log the transaction hash - console.log("\nTransaction Hash: ", tx); + /// @dev Will throw an error if we fail to load the contract's instance. + try { + Swaplace = await ethers.getContractAt( + "Swaplace", + SWAPLACE_ADDRESS, + signers[0], + ); + } catch (error) { + throw new Error( + `Error instancing the Swaplace contract. + Make sure if the network is correct and that the contract has the right + deployment address. Ultimately check for errors in the ABI by calling the + script 'npx hardhat clean' and then 'npx hardhat compile'. + ${error}`, + ); + } + + /// @dev Response from the `creatSwap` transaction. + let tx; + + /// @dev Catching any errors while creating the swap. + try { + tx = await Swaplace.acceptSwap(swapId, signers[0].address); + // @dev Wait for the transaction to be mined + await tx.wait(); + } catch (error) { + throw new Error( + `Error while accepting the swap. Make sure that the swap exists + and if the tokens were correctly approved before transfer. + ${error}`, + ); + } + + /// @dev Log the transactions + console.log("\nSwaplace accepted the Swap \nAt Tx %s", tx.hash); } main().catch((error) => { diff --git a/scripts/approve.ts b/scripts/approve.ts index 4870c8d..34faa3c 100755 --- a/scripts/approve.ts +++ b/scripts/approve.ts @@ -1,25 +1,120 @@ -import { ethers } from "ethers"; +import { ethers } from "hardhat"; +import { Contract } from "ethers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import dotenv from "dotenv"; +dotenv.config(); -export async function approve( - contract: ethers.Contract, - spender: string, - amountOrId: bigint, -) { +/// @notice This function will mint ERC20 or ERC721 tokens to the signer address. +/// The signer address is the first account in the list of accounts that were set in the hardhat config file. +async function main() { + /// @dev This is the list of mock deployments addresses that were stored in the `.env` file. + const ERC20_ADDRESS = process.env.ERC20_ADDRESS || 0x0; + const ERC721_ADDRESS = process.env.ERC721_ADDRESS || 0x0; + const ERC1155_ADDRESS = process.env.ERC1155_ADDRESS || 0x0; + /// @dev The Swaplace address also needs to be instance to receive the approvals. + const SWAPLACE_ADDRESS = process.env.SWAPLACE_ADDRESS || 0x0; + /// @dev Will throw an error if any of the addresses were not set in the `.env` file. + if ( + !ERC20_ADDRESS || + !ERC721_ADDRESS || + !SWAPLACE_ADDRESS || + !ERC1155_ADDRESS + ) { + throw new Error( + "Invalid ERC20, ERC721, ERC1155 or Swaplace address, please check if the addresse in the `.env` file is set up correctly.", + ); + } + + /// @dev Quantity of tokens to approve. + let amount = process.env.AMOUNT || 0x0; + + /// @dev Last token ID or choosed token ID to mint. + let tokenId = process.env.TOKEN_ID || 0x0; + + /// @dev No point in approving if the quantities and target token IDs are not set. + if (!tokenId || !amount) { + throw new Error( + "Invalid token ID or amount, please check if the values are set up correctly in the `.env` file.", + ); + } + + /// @dev This is the list of accounts that were set in the hardhat config file. + /// The first account will be performing the signing of the transactions, hence becoming the contract deployer. + let signers: SignerWithAddress[]; + + /// @dev The returned contract instance that will be deployed via the deploy function in utils. + let MockERC20: Contract; + let MockERC721: Contract; + let MockERC1155: Contract; + + /// @dev will throw an error if any of the accounts was not set up correctly. try { - if (ethers.utils.isAddress(spender)) { - // Approve tokens - const tx = await contract.approve(spender, amountOrId); - - // Wait for the transaction to be mined - await tx.wait(); - - // Return the transaction response - return tx; - } else { - throw new Error("Invalid Ethereum address"); - } + signers = await ethers.getSigners(); } catch (error) { - console.error(error); - process.exitCode = 1; + throw new Error( + `Error getting the first account from the list of accounts. Make sure it is + set up in correctly in hardhat.config.ts. + ${error}`, + ); } + + /// @dev Will throw an error if we fail to load the contract's instance. + try { + MockERC20 = await ethers.getContractAt( + "MockERC20", + ERC20_ADDRESS, + signers[0], + ); + MockERC721 = await ethers.getContractAt( + "MockERC721", + ERC721_ADDRESS, + signers[0], + ); + MockERC1155 = await ethers.getContractAt( + "MockERC1155", + ERC1155_ADDRESS, + signers[0], + ); + } catch (error) { + throw new Error( + `Error deploying one of the Mock Contracts. + Make sure if the network is correct and that the contract has the right + deployment address. Ultimately check for errors in the ABI by calling the + script 'npx hardhat clean' and then 'npx hardhat compile'. + ${error}`, + ); + } + + /// @dev Responses from the minting transactions. + let txErc20; + let txErc721; + let txErc1155; + + /// @dev We are approving the signer address to spend the amount of tokens. + try { + txErc20 = await MockERC20.approve(SWAPLACE_ADDRESS, amount); + txErc721 = await MockERC721.approve(SWAPLACE_ADDRESS, tokenId); + txErc1155 = await MockERC1155.setApprovalForAll(SWAPLACE_ADDRESS, true); + } catch (error) { + throw new Error( + `Error while approving the tokens. Make sure that the approve function is + correctly implemented in the contract and that the signer address is + correctly set up in the hardhat.config.ts file. Gas errors are common. + ${error}`, + ); + } + + /// @dev Log the transactions + console.log("\nERC20 Approved %s tokens \nAt Tx %s", amount, txErc20.hash); + console.log( + "\nERC721 Approved token ID #%s \nAt Tx %s", + tokenId, + txErc721.hash, + ); + console.log("\nERC1155 Approved all tokens \nAt Tx %s", txErc1155.hash); } + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/cancelSwap.ts b/scripts/cancelSwap.ts index e316b44..1b8cce0 100644 --- a/scripts/cancelSwap.ts +++ b/scripts/cancelSwap.ts @@ -1,27 +1,82 @@ import { ethers } from "hardhat"; -import abi from "../artifacts/contracts/Swaplace.sol/Swaplace.json"; +import { Contract } from "ethers"; +import { blocktimestamp } from "../test/utils/utils"; +import { Swap, composeSwap } from "../test/utils/SwapFactory"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; export async function main() { - // Get the first account from the list of accounts - const [signer] = await ethers.getSigners(); + /// @dev The Swaplace address also needs to be instance to receive the approvals. + const SWAPLACE_ADDRESS = process.env.SWAPLACE_ADDRESS || 0x0; + /// @dev Will throw an error if any of the addresses were not set in the `.env` file. + if (!SWAPLACE_ADDRESS) { + throw new Error( + "Invalid Swaplace address, please check if the addresse in the `.env` file is set up correctly.", + ); + } - // Get the Swaplace address from .env file - const swaplaceAddress: string = process.env.SWAPLACE_ADDRESS || ""; + /// @dev The swap to be approved + let swapId = process.env.SWAP_ID || 0x0; - // Get the Swaplace instance - const Swaplace = new ethers.Contract(swaplaceAddress, abi.abi, signer); + /// @dev No point in approving if the quantities and target token IDs are not set. + if (!swapId) { + throw new Error( + "Invalid swap ID, please check if the values are set up correctly in the `.env` file.", + ); + } - // Get the swap ID to be canceled - const swapId = 1; + /// @dev This is the list of accounts that were set in the hardhat config file. + /// The first account will be performing the signing of the transactions, hence becoming the contract deployer. + let signers: SignerWithAddress[]; - // Cancel the swap - const tx = await Swaplace.cancelSwap(swapId); + /// @dev The returned contract instance that will be deployed via the deploy function in utils. + let Swaplace: Contract; - // Wait for the transaction to be mined - await tx.wait(); + /// @dev will throw an error if any of the accounts was not set up correctly. + try { + signers = await ethers.getSigners(); + } catch (error) { + throw new Error( + `Error getting the first account from the list of accounts. Make sure it is + set up in correctly in hardhat.config.ts. + ${error}`, + ); + } - // Log the transaction hash - console.log("\nTransaction Hash: ", tx); + /// @dev Will throw an error if we fail to load the contract's instance. + try { + Swaplace = await ethers.getContractAt( + "Swaplace", + SWAPLACE_ADDRESS, + signers[0], + ); + } catch (error) { + throw new Error( + `Error instancing the Swaplace contract. + Make sure if the network is correct and that the contract has the right + deployment address. Ultimately check for errors in the ABI by calling the + script 'npx hardhat clean' and then 'npx hardhat compile'. + ${error}`, + ); + } + + /// @dev Response from the `creatSwap` transaction. + let tx; + + /// @dev Catching any errors while creating the swap. + try { + tx = await Swaplace.cancelSwap(swapId); + // @dev Wait for the transaction to be mined + await tx.wait(); + } catch (error) { + throw new Error( + `Error while canceling the swap. Make sure that the swap exists + and if the tokens were correctly approved before transfer. + ${error}`, + ); + } + + /// @dev Log the transactions + console.log("\nSwaplace canceled the Swap \nAt Tx %s", tx.hash); } main().catch((error) => { diff --git a/scripts/createSwap.ts b/scripts/createSwap.ts index c6330b4..28de0a7 100644 --- a/scripts/createSwap.ts +++ b/scripts/createSwap.ts @@ -1,35 +1,103 @@ import { ethers } from "hardhat"; -import { blocktimestamp } from "../test/utils/utils"; -import { Swap, composeSwap } from "../test/utils/SwapFactory"; -import abi from "../artifacts/contracts/Swaplace.sol/Swaplace.json"; +import { Contract } from "ethers"; +import { blocktimestamp, storeEnv } from "../test/utils/utils"; +import { + Swap, + composeSwap, + encodeAsset, + encodeConfig, +} from "../test/utils/SwapFactory"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; export async function main() { - // Get the first account from the list of accounts - const [signer] = await ethers.getSigners(); + /// @dev This is the list of mock deployments addresses that were stored in the `.env` file. + const ERC20_ADDRESS = process.env.ERC20_ADDRESS || 0x0; + const ERC721_ADDRESS = process.env.ERC721_ADDRESS || 0x0; + const ERC1155_ADDRESS = process.env.ERC1155_ADDRESS || 0x0; + /// @dev The Swaplace address also needs to be instance to receive the approvals. + const SWAPLACE_ADDRESS = process.env.SWAPLACE_ADDRESS || 0x0; + /// @dev Will throw an error if any of the addresses were not set in the `.env` file. + if ( + !ERC20_ADDRESS || + !ERC721_ADDRESS || + !SWAPLACE_ADDRESS || + !ERC1155_ADDRESS + ) { + throw new Error( + "Invalid ERC20, ERC721, ERC1155 or Swaplace address, please check if the addresses in the `.env` file are set up correctly.", + ); + } - // Get the Swaplace address from .env file - const swaplaceAddress: string = process.env.SWAPLACE_ADDRESS || ""; + /// @dev Quantity of tokens to approve. + let amount = process.env.AMOUNT || 0x0; - // Get the Swaplace instance - const Swaplace = new ethers.Contract(swaplaceAddress, abi.abi, signer); + /// @dev Last token ID or choosed token ID to mint. + let tokenId = process.env.TOKEN_ID || 0x0; - // Fill the Swap struct - const owner = signer.address; + /// @dev No point in approving if the quantities and target token IDs are not set. + if (!tokenId || !amount) { + throw new Error( + "Invalid token ID or amount, please check if the values are set up correctly in the `.env` file.", + ); + } + + /// @dev This is the list of accounts that were set in the hardhat config file. + /// The first account will be performing the signing of the transactions, hence becoming the contract deployer. + let signers: SignerWithAddress[]; + + /// @dev The returned contract instance that will be deployed via the deploy function in utils. + let Swaplace: Contract; + + /// @dev will throw an error if any of the accounts was not set up correctly. + try { + signers = await ethers.getSigners(); + } catch (error) { + throw new Error( + `Error getting the first account from the list of accounts. Make sure it is + set up in correctly in hardhat.config.ts. + ${error}`, + ); + } + + /// @dev Will throw an error if we fail to load the contract's instance. + try { + Swaplace = await ethers.getContractAt( + "Swaplace", + SWAPLACE_ADDRESS, + signers[0], + ); + } catch (error) { + throw new Error( + `Error instancing the Swaplace contract. + Make sure if the network is correct and that the contract has the right + deployment address. Ultimately check for errors in the ABI by calling the + script 'npx hardhat clean' and then 'npx hardhat compile'. + ${error}`, + ); + } + + /// @dev Fill the Swap struct and config + const owner = signers[0].address; const allowed = ethers.constants.AddressZero; const expiry = (await blocktimestamp()) * 2; + const recipient = 0; + const value = 0; - // Build the biding assets - const bidingAddr = ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"]; // USDC - const bidingAmountOrId = [1000]; + /// @dev Encode ERC1155 asset + const amountAndId = await encodeAsset(BigInt(tokenId), BigInt(amount)); - // Build the asking assets - const askingAddr = ["0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"]; // WBTC - const askingAmountOrId = [1]; + /// @dev Build the biding assets + const bidingAddr = [ERC20_ADDRESS, ERC1155_ADDRESS]; + const bidingAmountOrId = [BigInt(amount), amountAndId]; - // Pack the config together - const config = await Swaplace.packData(allowed, expiry); + /// @dev Build the asking assets + const askingAddr = [ERC721_ADDRESS]; + const askingAmountOrId = [BigInt(tokenId)]; - // Compose the swap + /// @dev Pack the config together + const config = await encodeConfig(allowed, expiry, recipient, value); + + /// @dev Compose the above swap into the Swap Struct const swap: Swap = await composeSwap( owner, config, @@ -39,14 +107,31 @@ export async function main() { askingAmountOrId, ); - // Create the swap - const tx = await Swaplace.createSwap(swap); + /// @dev Response from the `creatSwap` transaction. + let tx; - // Wait for the transaction to be mined - await tx.wait(); + /// @dev Catching any errors while creating the swap. + try { + tx = await Swaplace.createSwap(swap); + // @dev Wait for the transaction to be mined + await tx.wait(); + } catch (error) { + throw new Error( + `Error while creating the swap. Make sure that the struct is correctly + filled accordingly to the contract interface specification. + ${error}`, + ); + } + + /// @dev Log the transactions + console.log("\nSwaplace created the Swap \nAt Tx %s", tx.hash); - // Log the transaction hash - console.log("\nTransaction Hash: ", tx); + /// @dev Store the recently created swap and it's corresponding ID in the `.env` file. + const swapId = await Swaplace.totalSwaps(); + await storeEnv(swapId, "SWAP_ID", tx.hash); + + /// @dev Awaits for the transaction to be mined. + await tx.wait(); } main().catch((error) => { diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 9108fb9..472ba54 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -1,20 +1,43 @@ import { ethers } from "hardhat"; -import { deploy } from "../test/utils/utils"; +import { Contract } from "ethers"; +import { deploy, storeEnv } from "../test/utils/utils"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; async function main() { - // Get the first account from the list of accounts - const [signer] = await ethers.getSigners(); + /// @dev This is the list of accounts that were set in the hardhat config file. + /// The first account will be performing the signing of the transactions, hence becoming the contract deployer. + let signers: SignerWithAddress[]; - // Deploy in the currrent network and return the Swaplace instance - const Swaplace = await deploy("Swaplace", signer); + /// @dev The returned contract instance that will be deployed via the deploy function in utils. + let Swaplace: Contract; - // Log Contract address, the Tx then return the Contract instance + /// @dev will throw an error if any of the accounts was not set up correctly. + try { + signers = await ethers.getSigners(); + } catch (error) { + throw new Error( + `Error getting the first account from the list of accounts. Make sure it is + set up in correctly in hardhat.config.ts. + ${error}`, + ); + } + + // @dev Deploy in the currrent network in which the script was called and return the Swaplace instance. + Swaplace = await deploy("Swaplace", signers[0]); + + // @dev Log Contract address and the Tx hash which can be searched on Etherscan (or any other block explorer). console.log( "\nContract %s \nDeployed to %s \nAt Tx %s", "Swaplace", Swaplace.address, Swaplace.deployTransaction.hash, ); + + // @dev Store the contract address in the .env file. + await storeEnv(Swaplace.address, "SWAPLACE_ADDRESS", true); + + /// @dev Awaits for the transaction to be mined. + await Swaplace.deployed(); } main().catch((error) => { diff --git a/scripts/deployMock.ts b/scripts/deployMock.ts index 2dfdcf3..660c10e 100644 --- a/scripts/deployMock.ts +++ b/scripts/deployMock.ts @@ -1,34 +1,69 @@ import { ethers } from "hardhat"; -import { deploy } from "../test/utils/utils"; +import { Contract } from "ethers"; +import { deploy, storeEnv } from "../test/utils/utils"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -export async function deployMock(signer: any) { - // Deploy in the currrent network and return the contract instance - const MockERC20 = await deploy("MockERC20", signer); - const MockERC721 = await deploy("MockERC721", signer); +async function main() { + /// @dev This is the list of accounts that were set in the hardhat config file. + /// The first account will be performing the signing of the transactions, hence becoming the contract deployer. + let signers: SignerWithAddress[]; - // Log Contract address, the Tx then return the Contract instance of MockERC20 + /// @dev The returned contract instance that will be deployed via the deploy function in utils. + let MockERC20: Contract; + let MockERC721: Contract; + let MockERC1155: Contract; + + /// @dev will throw an error if any of the accounts was not set up correctly. + try { + signers = await ethers.getSigners(); + } catch (error) { + throw new Error( + `Error getting the first account from the list of accounts. Make sure it is + set up in correctly in hardhat.config.ts. + ${error}`, + ); + } + + // @dev Deploy in the currrent network in which the script was called and return the Swaplace instance. + // We are deploying both contracts to test the user flux with the entire functionality. + MockERC20 = await deploy("MockERC20", signers[0]); + MockERC721 = await deploy("MockERC721", signers[0]); + MockERC1155 = await deploy("MockERC1155", signers[0]); + + // @dev Log Contract address and the Tx hash which can be searched on Etherscan (or any other block explorer). console.log( - "\nDEPLOY:\nContract %s \nDeployed to %s \nAt Tx %s", + "\nContract %s \nDeployed to %s \nAt Tx %s", "MockERC20", MockERC20.address, MockERC20.deployTransaction.hash, ); - // Log Contract address, the Tx then return the Contract instance of MockERC721 console.log( - "\nContract %s \nDeployed to %s \nAt Tx %s", + "\nContract %s \nDeployed to %s \nAt Tx %s\n", "MockERC721", MockERC721.address, MockERC721.deployTransaction.hash, ); - // Return the transaction response - return { MockERC20, MockERC721 }; + console.log( + "\nContract %s \nDeployed to %s \nAt Tx %s\n", + "MockERC1155", + MockERC1155.address, + MockERC1155.deployTransaction.hash, + ); + + /// @dev Store the contract addresses in the .env file. + await storeEnv(MockERC20.address, "ERC20_ADDRESS", true); + await storeEnv(MockERC721.address, "ERC721_ADDRESS", true); + await storeEnv(MockERC1155.address, "ERC1155_ADDRESS", true); + + /// @dev Awaits for the transaction to be mined. + await MockERC20.deployed(); + await MockERC721.deployed(); + await MockERC1155.deployed(); } -ethers.getSigners().then((signers) => { - deployMock(signers[0]).catch((error) => { - console.error(error); - process.exitCode = 1; - }); +main().catch((error) => { + console.error(error); + process.exitCode = 1; }); diff --git a/scripts/getSwap.ts b/scripts/getSwap.ts deleted file mode 100644 index dc52eab..0000000 --- a/scripts/getSwap.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ethers } from "hardhat"; -import abi from "../artifacts/contracts/Swaplace.sol/Swaplace.json"; - -export async function main() { - // Get the first account from the list of accounts - const [signer] = await ethers.getSigners(); - - // Get the Swaplace address from .env file - const swaplaceAddress: string = process.env.SWAPLACE_ADDRESS || ""; - - // Get the Swaplace instance - const Swaplace = new ethers.Contract(swaplaceAddress, abi.abi, signer); - - // Get the swap ID - const swapId = 1; - - // Get the swap - const swap = await Swaplace.getSwap(swapId); - - // Log the swap - console.log("\nSwap %: ", swapId); - console.log(swap); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/scripts/mint.ts b/scripts/mint.ts index 8882999..64ee9a0 100644 --- a/scripts/mint.ts +++ b/scripts/mint.ts @@ -1,25 +1,123 @@ -import { ethers } from "ethers"; +import { ethers } from "hardhat"; +import { Contract } from "ethers"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import dotenv from "dotenv"; +import { storeEnv } from "../test/utils/utils"; +dotenv.config(); -export async function mint( - contract: ethers.Contract, - amountOrId: bigint, - receiver: string, -) { +/// @notice This function will mint ERC20 or ERC721 tokens to the signer address. +/// The signer address is the first account in the list of accounts that were set in the hardhat config file. +async function main() { + /// @dev This is the list of mock deployments addresses that were stored in the `.env` file. + const ERC20_ADDRESS = process.env.ERC20_ADDRESS || 0x0; + const ERC721_ADDRESS = process.env.ERC721_ADDRESS || 0x0; + const ERC1155_ADDRESS = process.env.ERC1155_ADDRESS || 0x0; + /// @dev Will throw an error if any of the addresses were not set in the `.env` file. + if (!ERC20_ADDRESS || !ERC721_ADDRESS || !ERC1155_ADDRESS) { + throw new Error( + "Invalid ERC20 or ERC721 or ERC1155 address, please check if the addresses in the `.env` file are set up correctly.", + ); + } + + /// @dev This is the list of accounts that were set in the hardhat config file. + /// The first account will be performing the signing of the transactions, hence becoming the contract deployer. + let signers: SignerWithAddress[]; + + /// @dev The returned contract instance that will be deployed via the deploy function in utils. + let MockERC20: Contract; + let MockERC721: Contract; + let MockERC1155: Contract; + + /// @dev will throw an error if any of the accounts was not set up correctly. + try { + signers = await ethers.getSigners(); + } catch (error) { + throw new Error( + `Error getting the first account from the list of accounts. Make sure it is + set up in correctly in hardhat.config.ts. + ${error}`, + ); + } + + /// @dev Will throw an error if we fail to load the contract instances. try { - if (ethers.utils.isAddress(receiver)) { - // Mint ERC tokens - const tx = await contract.mintTo(receiver, amountOrId); - - // Wait for the transaction to be mined - await tx.wait(); - - // Return the transaction response - return tx; - } else { - throw new Error("Invalid Ethereum address"); - } + MockERC20 = await ethers.getContractAt( + "MockERC20", + ERC20_ADDRESS, + signers[0], + ); + MockERC721 = await ethers.getContractAt( + "MockERC721", + ERC721_ADDRESS, + signers[0], + ); + MockERC1155 = await ethers.getContractAt( + "MockERC1155", + ERC1155_ADDRESS, + signers[0], + ); } catch (error) { - console.error(error); - process.exitCode = 1; + throw new Error( + `Error deploying one of the Mock Contracts. + Make sure if the network is correct and that the contract has the right + deployment address. Ultimately check for errors in the ABI by calling the + script 'npx hardhat clean' and then 'npx hardhat compile'. + ${error}`, + ); } + + /// @dev Quantity of tokens to mint. + let amount = process.env.AMOUNT || 1000; + + /// @dev We fetch last token id to avoid minting the same token. + let tokenId = process.env.TOKEN_ID || 1; + tokenId = Number(tokenId) + 1; // Increment the token id by 1. + + /// @dev Responses from the minting transactions. + let txErc20; + let txErc721; + let txErc1155; + + /// @dev Minting function will throw an error if the minting fails. + /// We are minting for the first signer of `hardhat.config.ts` 1000 + /// tokens of ERC20 and the last token id for ERC721. + /// We start the mint from the last token id + 1 because it starts from 0. + try { + txErc20 = await MockERC20.mint(signers[0].address, amount); + txErc721 = await MockERC721.mint(signers[0].address, tokenId); + txErc1155 = await MockERC1155.mint(signers[0].address, tokenId, amount); + } catch (error) { + throw new Error( + `Error while minting tokens. Make sure that the minting function is + correctly implemented in the contract and that the signer address is + correctly set up in the hardhat.config.ts file. Gas errors are common. + ${error}`, + ); + } + + /// @dev Log the transactions + console.log("\nERC20 Minted %s tokens \nAt Tx %s", amount, txErc20.hash); + console.log( + "\nERC721 Minted token ID #%s \nAt Tx %s", + tokenId, + txErc721.hash, + ); + console.log( + "\nERC1155 Minted %s tokens with ID #%s \nAt Tx %s", + amount, + tokenId, + txErc1155.hash, + ); + + await storeEnv(tokenId, "TOKEN_ID", false); + + /// @dev Awaits for the transaction to be mined. + await txErc20.wait(); + await txErc721.wait(); + await txErc1155.wait(); } + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/test/TestMockContracts.test.ts b/test/TestMockContracts.test.ts index 5db1bf7..b7be7eb 100644 --- a/test/TestMockContracts.test.ts +++ b/test/TestMockContracts.test.ts @@ -8,6 +8,7 @@ describe("Swaplace", async function () { // The deployed contracts let MockERC20: Contract; let MockERC721: Contract; + let MockERC1155: Contract; // The signers of the test let deployer: SignerWithAddress; @@ -18,27 +19,32 @@ describe("Swaplace", async function () { [deployer, owner, acceptee] = await ethers.getSigners(); MockERC20 = await deploy("MockERC20", deployer); MockERC721 = await deploy("MockERC721", deployer); + MockERC1155 = await deploy("MockERC1155", deployer); }); it("Should test the {mint} function", async function () { - // Testing the mint of ERC20 - await MockERC20.mintTo(owner.address, 1000); + await MockERC20.mint(owner.address, 1000); expect(await MockERC20.balanceOf(owner.address)).to.be.equals(1000); - // Testing the mint of ERC721 - await MockERC721.mintTo(owner.address, 1); + await MockERC721.mint(owner.address, 1); expect(await MockERC721.balanceOf(owner.address)).to.be.equals(1); + + await MockERC1155.mint(owner.address, 1, 5); }); it("Should test the {approve} function", async function () { - // Testing the approval of ERC20 await MockERC20.connect(owner).approve(acceptee.address, 1000); expect( await MockERC20.allowance(owner.address, acceptee.address), ).to.be.equals("1000"); - // Testing the approval of ERC721 + await MockERC721.connect(owner).approve(acceptee.address, 1); expect(await MockERC721.getApproved(1)).to.be.equals(acceptee.address); + + await MockERC1155.connect(owner).setApprovalForAll(acceptee.address, true); + expect( + await MockERC1155.isApprovedForAll(owner.address, acceptee.address), + ).to.be.equals(true); }); it("Should test the {transferFrom} function", async function () { @@ -54,5 +60,14 @@ describe("Swaplace", async function () { ); expect(await MockERC721.balanceOf(owner.address)).to.be.equals(0); expect(await MockERC721.balanceOf(acceptee.address)).to.be.equals(1); + // Testing the transfer of ERC1155 + await MockERC1155.connect(owner).safeTransferFrom( + owner.address, + acceptee.address, + 1, + 5, + "0x", + ); + expect(await MockERC1155.balanceOf(acceptee.address, 1)).to.be.equals(5); }); }); diff --git a/test/TestSwapFactory.test.ts b/test/TestSwapFactory.test.ts index bb1079d..12e88f0 100644 --- a/test/TestSwapFactory.test.ts +++ b/test/TestSwapFactory.test.ts @@ -1,8 +1,16 @@ import { expect } from "chai"; -import { Contract } from "ethers"; +import { BigNumber, Contract } from "ethers"; import { ethers } from "hardhat"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { Asset, makeAsset, makeSwap, composeSwap } from "./utils/SwapFactory"; +import { + Asset, + makeAsset, + makeSwap, + composeSwap, + encodeConfig, + decodeConfig, + Swap, +} from "./utils/SwapFactory"; import { blocktimestamp, deploy } from "./utils/utils"; describe("Swaplace Factory", async function () { @@ -10,6 +18,7 @@ describe("Swaplace Factory", async function () { let Swaplace: Contract; let MockERC20: Contract; let MockERC721: Contract; + let MockERC1155: Contract; // The signers of the test let deployer: SignerWithAddress; @@ -23,16 +32,17 @@ describe("Swaplace Factory", async function () { Swaplace = await deploy("Swaplace", deployer); MockERC20 = await deploy("MockERC20", deployer); MockERC721 = await deploy("MockERC721", deployer); + MockERC1155 = await deploy("MockERC1155", deployer); }); it("Should be able to {makeAsset} for ERC20 and ERC721", async function () { var asset: Asset = await makeAsset(MockERC20.address, 1000); expect(asset.addr).to.be.equals(MockERC20.address); - expect(asset.amountOrId).to.be.equals("1000"); + expect(asset.amountOrId).to.be.equals(1000); var asset: Asset = await makeAsset(MockERC721.address, 1); expect(asset.addr).to.be.equals(MockERC721.address); - expect(asset.amountOrId).to.be.equals("1"); + expect(asset.amountOrId).to.be.equals(1); }); it("Should be able to {makeAsset} in the off-chain matching on-chain", async function () { @@ -40,16 +50,48 @@ describe("Swaplace Factory", async function () { var asset: Asset = await makeAsset(MockERC20.address, 1000); expect(asset.addr).to.be.equals(MockERC20.address); - expect(asset.amountOrId).to.be.equals("1000"); + expect(asset.amountOrId).to.be.equals(1000); + }); + + it("Should be able to encode and decode config using off-chain", async function () { + const currentTimestamp = (await blocktimestamp()) + 2000000; + const configOnChain = await Swaplace.encodeConfig( + Swaplace.address, + currentTimestamp, + 0, + 0, + ); + const configOffChain = await encodeConfig( + Swaplace.address, + currentTimestamp, + 0, + 0, + ); + expect(configOnChain).to.be.equals(configOffChain); + + const [allowed, expiry, recipient, value] = await Swaplace.decodeConfig( + configOnChain, + ); + const decodedConfig = await decodeConfig(configOffChain); + + expect(BigInt(allowed)).to.be.equals(decodedConfig.allowed); + expect(expiry).to.be.equals(decodedConfig.expiry); + expect(recipient).to.be.equals(decodedConfig.recipient); + expect(value).to.be.equals(decodedConfig.value); }); it("Should be able to {makeSwap} with ERC20 and ERC721", async function () { - const currentTimestamp = (await blocktimestamp()) * 2; + const currentTimestamp = (await blocktimestamp()) + 2000000; const ERC20Asset: Asset = await makeAsset(MockERC20.address, 1000); const ERC721Asset: Asset = await makeAsset(MockERC721.address, 1); - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); const swap = await makeSwap( owner.address, @@ -58,7 +100,7 @@ describe("Swaplace Factory", async function () { [ERC721Asset], ); - const [allowed, expiry] = await Swaplace.parseData(swap.config); + const [allowed, expiry, ,] = await Swaplace.decodeConfig(swap.config); expect(swap.owner).to.be.equals(owner.address); expect(expiry).to.be.equals(currentTimestamp); @@ -68,12 +110,17 @@ describe("Swaplace Factory", async function () { }); it("Should be able to {makeSwap} in the off-chain matching on-chain", async function () { - const currentTimestamp = (await blocktimestamp()) * 2; + const currentTimestamp = (await blocktimestamp()) + 2000000; const ERC20Asset: Asset = await makeAsset(MockERC20.address, 1000); const ERC721Asset: Asset = await makeAsset(MockERC721.address, 1); - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); const swap = await makeSwap( owner.address, @@ -86,12 +133,14 @@ describe("Swaplace Factory", async function () { owner.address, zeroAddress, currentTimestamp, + 0, + 0, [ERC20Asset], [ERC721Asset], ); - const [allowed, expiry] = await Swaplace.parseData(swap.config); - const [onChainAllowed, onChainExpiry] = await Swaplace.parseData( + const [allowed, expiry, ,] = await Swaplace.decodeConfig(swap.config); + const [onChainAllowed, onChainExpiry, ,] = await Swaplace.decodeConfig( onChainSwap.config, ); @@ -110,13 +159,73 @@ describe("Swaplace Factory", async function () { ); }); + it("Should be able to {makeSwap} with native ethers value", async function () { + const currentTimestamp = (await blocktimestamp()) + 2000000; + + const ERC20Asset: Asset = await makeAsset(MockERC20.address, 1000); + const ERC721Asset: Asset = await makeAsset(MockERC721.address, 1); + + const valueToSend: BigNumber = ethers.utils.parseEther("0.2"); + + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 1, + valueToSend.div(1e12), + ); + + const swap = await makeSwap( + owner.address, + config, + [ERC20Asset], + [ERC721Asset], + ); + + const onChainSwap = await Swaplace.makeSwap( + owner.address, + zeroAddress, + currentTimestamp, + 1, + valueToSend.div(1e12), + [ERC20Asset], + [ERC721Asset], + ); + + const [allowed, expiry, recipient, value] = await Swaplace.decodeConfig( + swap.config, + ); + const [onChainAllowed, onChainExpiry, onChainRecipient, onChainValue] = + await Swaplace.decodeConfig(onChainSwap.config); + + expect(swap.owner).to.be.equals(onChainSwap.owner); + expect(expiry).to.be.equals(onChainExpiry); + expect(allowed).to.be.equals(onChainAllowed); + expect(recipient).to.be.equals(onChainRecipient); + expect(value).to.be.equals(onChainValue); + + expect(swap.biding[0].addr).to.be.equals(onChainSwap.biding[0].addr); + expect(swap.biding[0].amountOrId).to.be.equals( + onChainSwap.biding[0].amountOrId, + ); + + expect(swap.asking[0].addr).to.be.equals(onChainSwap.asking[0].addr); + expect(swap.asking[0].amountOrId).to.be.equals( + onChainSwap.asking[0].amountOrId, + ); + }); + it("Should be able to {makeSwap} with multiple assets", async function () { - const currentTimestamp = (await blocktimestamp()) * 2; + const currentTimestamp = (await blocktimestamp()) + 2000000; const ERC20Asset = await makeAsset(MockERC20.address, 1000); const ERC721Asset = await makeAsset(MockERC721.address, 1); - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); const swap = await makeSwap( owner.address, @@ -125,7 +234,7 @@ describe("Swaplace Factory", async function () { [ERC20Asset, ERC721Asset], ); - const [, expiry] = await Swaplace.parseData(swap.config); + const [, expiry, ,] = await Swaplace.decodeConfig(swap.config); expect(swap.owner).to.be.equals(owner.address); expect(expiry).to.be.equals(expiry); @@ -135,8 +244,150 @@ describe("Swaplace Factory", async function () { expect(swap.asking[1]).to.be.equals(ERC721Asset); }); + it("Should be able to {makeSwap} with ERC1155 tokens", async function () { + const bidingAddr = [MockERC1155.address]; + const tokenId = 1; + const amount = 3; + const amountAndId = await Swaplace.encodeAsset(tokenId, amount); + const bidingAmountOrId = [amountAndId]; + + const askingAddr = [MockERC721.address]; + const askingAmountOrId = [50]; + + const ERC1155Asset: Asset = await makeAsset( + bidingAddr[0], + bidingAmountOrId[0], + ); + const ERC721Asset: Asset = await makeAsset( + askingAddr[0], + askingAmountOrId[0], + ); + + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); + + const swap = await makeSwap( + owner.address, + config, + [ERC1155Asset], + [ERC721Asset], + ); + + const onChainSwap = await Swaplace.makeSwap( + owner.address, + zeroAddress, + currentTimestamp, + 0, + 0, + [ERC1155Asset], + [ERC721Asset], + ); + + const [allowed, expiry, recipient, value] = await Swaplace.decodeConfig( + swap.config, + ); + + const [onChainAllowed, onChainExpiry, onChainRecipient, onChainValue] = + await Swaplace.decodeConfig(onChainSwap.config); + + expect(swap.owner).to.be.equals(onChainSwap.owner); + expect(expiry).to.be.equals(onChainExpiry); + expect(allowed).to.be.equals(onChainAllowed); + expect(recipient).to.be.equals(onChainRecipient); + expect(value).to.be.equals(onChainValue); + expect(swap.biding[0].addr).to.be.equals(onChainSwap.biding[0].addr); + expect(swap.biding[0].amountOrId).to.be.equals( + onChainSwap.biding[0].amountOrId, + ); + + expect(swap.asking[0].addr).to.be.equals(onChainSwap.asking[0].addr); + expect(swap.asking[0].amountOrId).to.be.equals( + onChainSwap.asking[0].amountOrId, + ); + }); + + it("Should be able to {composeSwap} using ERC1155", async function () { + const bidingAddr = [MockERC1155.address]; + const tokenId = 1; + const amount = 3; + const amountAndId = await Swaplace.encodeAsset(tokenId, amount); + const bidingAmountOrId = [amountAndId]; + + const askingAddr = [MockERC721.address]; + const askingAmountOrId = [50]; + + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + + const [allowed, expiry, recipient, value] = await Swaplace.decodeConfig( + swap.config, + ); + + expect(swap.owner).to.be.equals(owner.address); + expect(allowed).to.be.equals(zeroAddress); + expect(expiry).to.be.equals(expiry); + expect(recipient).to.be.equals(0); + expect(value).to.be.equals(0); + expect(swap.biding[0].addr).to.be.equals(bidingAddr[0]); + expect(swap.biding[0].amountOrId).to.be.equals(bidingAmountOrId[0]); + + expect(swap.asking[0].addr).to.be.equals(askingAddr[0]); + expect(swap.asking[0].amountOrId).to.be.equals(askingAmountOrId[0]); + }); + it("Should be able to {composeSwap} using both ERC20, ERC721", async function () { - const currentTimestamp = (await blocktimestamp()) * 2; + const currentTimestamp = (await blocktimestamp()) + 2000000; + + const bidingAddr = [MockERC20.address, MockERC721.address]; + const bidingAmountOrId = [1000, 1]; + + const askingAddr = [MockERC721.address]; + const askingAmountOrId = [2]; + + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); + + const swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + + const [allowed, expiry, ,] = await Swaplace.decodeConfig(swap.config); + + expect(swap.owner).to.be.equals(owner.address); + expect(allowed).to.be.equals(zeroAddress); + expect(expiry).to.be.equals(expiry); + }); + + it("Should be able to {composeSwap} using native ethers value", async function () { + const currentTimestamp = (await blocktimestamp()) + 2000000; const bidingAddr = [MockERC20.address, MockERC721.address]; const bidingAmountOrId = [1000, 1]; @@ -144,7 +395,14 @@ describe("Swaplace Factory", async function () { const askingAddr = [MockERC721.address]; const askingAmountOrId = [2]; - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const valueToSend: BigNumber = ethers.utils.parseEther("1"); + + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + valueToSend.div(1e12), + ); const swap = await composeSwap( owner.address, @@ -155,11 +413,15 @@ describe("Swaplace Factory", async function () { askingAmountOrId, ); - const [allowed, expiry] = await Swaplace.parseData(swap.config); + const [allowed, expiry, recipient, value] = await Swaplace.decodeConfig( + swap.config, + ); expect(swap.owner).to.be.equals(owner.address); expect(allowed).to.be.equals(zeroAddress); expect(expiry).to.be.equals(expiry); + expect(recipient).to.be.equals(0); + expect(value).to.be.equals(valueToSend.div(1e12)); }); it("Should revert using {composeSwap} without minimum expiry", async function () { @@ -172,7 +434,7 @@ describe("Swaplace Factory", async function () { const askingAmountOrId = [2]; try { - const config = await Swaplace.packData(zeroAddress, expiry); + const config = await Swaplace.encodeConfig(zeroAddress, expiry, 0, 0); await composeSwap( owner.address, config, @@ -187,7 +449,7 @@ describe("Swaplace Factory", async function () { }); it("Should revert using {composeSwap} with owner as address zero", async function () { - const currentTimestamp = (await blocktimestamp()) * 2; + const currentTimestamp = (await blocktimestamp()) + 2000000; const bidingAddr = [MockERC20.address]; const bidingAmountOrId = [1000]; @@ -196,7 +458,12 @@ describe("Swaplace Factory", async function () { const askingAmountOrId = [2]; try { - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); await composeSwap( zeroAddress, config, @@ -211,7 +478,7 @@ describe("Swaplace Factory", async function () { }); it("Should revert using {composeSwap} with empty assets", async function () { - const currentTimestamp = (await blocktimestamp()) * 2; + const currentTimestamp = (await blocktimestamp()) + 2000000; const bidingAddr = [MockERC20.address]; const bidingAmountOrId = [1000]; @@ -220,7 +487,12 @@ describe("Swaplace Factory", async function () { const askingAmountOrId: any[] = []; try { - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); await composeSwap( owner.address, config, @@ -235,7 +507,7 @@ describe("Swaplace Factory", async function () { }); it("Should revert using {composeSwap} with empty assets length", async function () { - const currentTimestamp = (await blocktimestamp()) * 2; + const currentTimestamp = (await blocktimestamp()) + 2000000; const bidingAddr = [MockERC20.address]; const bidingAmountOrId = [1000]; @@ -244,7 +516,12 @@ describe("Swaplace Factory", async function () { const askingAmountOrId = [1, 999, 777]; try { - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); await composeSwap( owner.address, config, @@ -258,12 +535,17 @@ describe("Swaplace Factory", async function () { } }); - it("Should ensure packData() and parseData() return the right values", async function () { - const currentTimestamp = (await blocktimestamp()) * 2; + it("Should ensure encodeConfig() and decodeConfig() return the right values", async function () { + const currentTimestamp = (await blocktimestamp()) + 2000000; - const config = await Swaplace.packData(acceptee.address, currentTimestamp); + const config = await Swaplace.encodeConfig( + acceptee.address, + currentTimestamp, + 0, + 0, + ); - const [allowed, expiry] = await Swaplace.parseData(config); + const [allowed, expiry, ,] = await Swaplace.decodeConfig(config); expect(allowed).to.be.equals(acceptee.address); expect(expiry).to.be.equals(currentTimestamp); diff --git a/test/TestSwaplace.test.ts b/test/TestSwaplace.test.ts index 68c5b2a..13be114 100644 --- a/test/TestSwaplace.test.ts +++ b/test/TestSwaplace.test.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { Contract } from "ethers"; +import { BigNumber, Contract } from "ethers"; import { ethers, network } from "hardhat"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { Asset, Swap, composeSwap } from "./utils/SwapFactory"; @@ -10,6 +10,7 @@ describe("Swaplace", async function () { let Swaplace: Contract; let MockERC20: Contract; let MockERC721: Contract; + let MockERC1155: Contract; // The signers of the test let deployer: SignerWithAddress; @@ -32,8 +33,14 @@ describe("Swaplace", async function () { const askingAddr = [MockERC20.address]; const askingAmountOrId = [50]; - const currentTimestamp = (await blocktimestamp()) * 2; - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const currentTimestamp = (await blocktimestamp()) + 1000000; + + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); const swap: Swap = await composeSwap( owner.address, @@ -52,10 +59,260 @@ describe("Swaplace", async function () { Swaplace = await deploy("Swaplace", deployer); MockERC20 = await deploy("MockERC20", deployer); MockERC721 = await deploy("MockERC721", deployer); + MockERC1155 = await deploy("MockERC1155", deployer); }); describe("Creating Swaps", () => { context("Creating different types of Swaps", () => { + it("Should be able to create a 1-1 swap with ERC1155", async function () { + const bidingAddr = [MockERC1155.address]; + const tokenId = 1; + const amount = 3; + const amountAndId = await Swaplace.encodeAsset(tokenId, amount); + const bidingAmountOrId = [amountAndId]; + + const askingAddr = [MockERC721.address]; + const askingAmountOrId = [50]; + + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + allowed.address, + currentTimestamp, + 0, + 0, + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + + await MockERC1155.mint(owner.address, tokenId, amount); + await MockERC1155.connect(owner).setApprovalForAll( + Swaplace.address, + true, + ); + await MockERC721.mint(allowed.address, askingAmountOrId[0]); + await MockERC721.connect(allowed).approve( + Swaplace.address, + askingAmountOrId[0], + ); + + const nextSwapId = Number(await Swaplace.totalSwaps()) + 1; + + await expect(await Swaplace.connect(owner).createSwap(swap)) + .to.emit(Swaplace, "SwapCreated") + .withArgs(nextSwapId, owner.address, allowed.address); + + await expect( + await Swaplace.connect(allowed).acceptSwap( + nextSwapId, + receiver.address, + ), + ) + .to.emit(Swaplace, "SwapAccepted") + .withArgs(nextSwapId, owner.address, allowed.address); + + expect( + await MockERC1155.balanceOf(owner.address, tokenId), + ).to.be.equals(0); + expect( + await MockERC1155.balanceOf(receiver.address, tokenId), + ).to.be.equals(amount); + expect(await MockERC721.ownerOf(askingAmountOrId[0])).to.be.equals( + owner.address, + ); + expect(await MockERC721.balanceOf(allowed.address)).to.be.equals(0); + }); + + it("Should be able to create a 1-N swap with ERC1155", async function () { + const bidingAddr = [ + MockERC1155.address, + MockERC1155.address, + MockERC1155.address, + ]; + const tokenId1 = 69; + const amount1 = 3; + const tokenId2 = 2; + const amount2 = 6; + const tokenId3 = 3; + const amount3 = 9; + const amountAndId1 = await Swaplace.encodeAsset(tokenId1, amount1); + const amountAndId2 = await Swaplace.encodeAsset(tokenId2, amount2); + const amountAndId3 = await Swaplace.encodeAsset(tokenId3, amount3); + const bidingAmountOrId = [amountAndId1, amountAndId2, amountAndId3]; + + const askingAddr = [MockERC721.address]; + const askingAmountOrId = [69]; + + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + allowed.address, + currentTimestamp, + 0, + 0, + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + + await MockERC1155.mint(owner.address, tokenId1, amount1); + await MockERC1155.mint(owner.address, tokenId2, amount2); + await MockERC1155.mint(owner.address, tokenId3, amount3); + await MockERC1155.connect(owner).setApprovalForAll( + Swaplace.address, + true, + ); + await MockERC721.mint(allowed.address, askingAmountOrId[0]); + await MockERC721.connect(allowed).approve( + Swaplace.address, + askingAmountOrId[0], + ); + + const nextSwapId = Number(await Swaplace.totalSwaps()) + 1; + + await expect(await Swaplace.connect(owner).createSwap(swap)) + .to.emit(Swaplace, "SwapCreated") + .withArgs(nextSwapId, owner.address, allowed.address); + + await expect( + await Swaplace.connect(allowed).acceptSwap( + nextSwapId, + receiver.address, + ), + ) + .to.emit(Swaplace, "SwapAccepted") + .withArgs(nextSwapId, owner.address, allowed.address); + + expect( + await MockERC1155.balanceOf(owner.address, tokenId1), + ).to.be.equals(0); + expect( + await MockERC1155.balanceOf(owner.address, tokenId2), + ).to.be.equals(0); + expect( + await MockERC1155.balanceOf(owner.address, tokenId3), + ).to.be.equals(0); + expect( + await MockERC1155.balanceOf(receiver.address, tokenId1), + ).to.be.equals(amount1); + expect( + await MockERC1155.balanceOf(receiver.address, tokenId2), + ).to.be.equals(amount2); + expect( + await MockERC1155.balanceOf(receiver.address, tokenId3), + ).to.be.equals(amount3); + expect(await MockERC721.ownerOf(askingAmountOrId[0])).to.be.equals( + owner.address, + ); + expect(await MockERC721.balanceOf(allowed.address)).to.be.equals(0); + }); + + it("Should be able to create a N-N swap with ERC1155", async function () { + const bidingAddr = [ + MockERC1155.address, + MockERC1155.address, + MockERC1155.address, + ]; + const tokenId1 = 4; + const amount1 = 69; + const tokenId2 = 5; + const amount2 = 69; + const tokenId3 = 6; + const amount3 = 69; + const amountAndId1 = await Swaplace.encodeAsset(tokenId1, amount1); + const amountAndId2 = await Swaplace.encodeAsset(tokenId2, amount2); + const amountAndId3 = await Swaplace.encodeAsset(tokenId3, amount3); + const bidingAmountOrId = [amountAndId1, amountAndId2, amountAndId3]; + + const askingAddr = [ + MockERC721.address, + MockERC721.address, + MockERC721.address, + ]; + const askingAmountOrId = [59, 79, 89]; + + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + allowed.address, + currentTimestamp, + 0, + 0, + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + + await MockERC1155.mint(owner.address, tokenId1, amount1); + await MockERC1155.mint(owner.address, tokenId2, amount2); + await MockERC1155.mint(owner.address, tokenId3, amount3); + await MockERC1155.connect(owner).setApprovalForAll( + Swaplace.address, + true, + ); + await MockERC721.mint(allowed.address, askingAmountOrId[0]); + await MockERC721.mint(allowed.address, askingAmountOrId[1]); + await MockERC721.mint(allowed.address, askingAmountOrId[2]); + await MockERC721.connect(allowed).setApprovalForAll( + Swaplace.address, + true, + ); + + const nextSwapId = Number(await Swaplace.totalSwaps()) + 1; + + await expect(await Swaplace.connect(owner).createSwap(swap)) + .to.emit(Swaplace, "SwapCreated") + .withArgs(nextSwapId, owner.address, allowed.address); + + await expect( + await Swaplace.connect(allowed).acceptSwap( + nextSwapId, + receiver.address, + ), + ) + .to.emit(Swaplace, "SwapAccepted") + .withArgs(nextSwapId, owner.address, allowed.address); + + expect( + await MockERC1155.balanceOf(owner.address, tokenId1), + ).to.be.equals(0); + expect( + await MockERC1155.balanceOf(owner.address, tokenId2), + ).to.be.equals(0); + expect( + await MockERC1155.balanceOf(owner.address, tokenId3), + ).to.be.equals(0); + expect( + await MockERC1155.balanceOf(receiver.address, tokenId1), + ).to.be.equals(amount1); + expect( + await MockERC1155.balanceOf(receiver.address, tokenId2), + ).to.be.equals(amount2); + expect( + await MockERC1155.balanceOf(receiver.address, tokenId3), + ).to.be.equals(amount3); + expect(await MockERC721.ownerOf(askingAmountOrId[0])).to.be.equals( + owner.address, + ); + expect(await MockERC721.balanceOf(allowed.address)).to.be.equals(0); + }); + it("Should be able to create a 1-1 swap with ERC20", async function () { const bidingAddr = [MockERC20.address]; const bidingAmountOrId = [50]; @@ -63,8 +320,13 @@ describe("Swaplace", async function () { const askingAddr = [MockERC20.address]; const askingAmountOrId = [50]; - const currentTimestamp = (await blocktimestamp()) * 2; - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); const swap: Swap = await composeSwap( owner.address, @@ -91,8 +353,13 @@ describe("Swaplace", async function () { ]; const askingAmountOrId = [50, 100, 150]; - const currentTimestamp = (await blocktimestamp()) * 2; - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); const swap: Swap = await composeSwap( owner.address, @@ -123,8 +390,13 @@ describe("Swaplace", async function () { ]; const askingAmountOrId = [50, 100, 150]; - const currentTimestamp = (await blocktimestamp()) * 2; - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); const swap: Swap = await composeSwap( owner.address, @@ -147,8 +419,13 @@ describe("Swaplace", async function () { const askingAddr = [MockERC721.address]; const askingAmountOrId = [4]; - const currentTimestamp = (await blocktimestamp()) * 2; - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); const swap: Swap = await composeSwap( owner.address, @@ -175,8 +452,13 @@ describe("Swaplace", async function () { ]; const askingAmountOrId = [4, 5, 6]; - const currentTimestamp = (await blocktimestamp()) * 2; - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); const swap: Swap = await composeSwap( owner.address, @@ -207,8 +489,13 @@ describe("Swaplace", async function () { ]; const askingAmountOrId = [4, 5, 6]; - const currentTimestamp = (await blocktimestamp()) * 2; - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); const swap: Swap = await composeSwap( owner.address, @@ -223,14 +510,119 @@ describe("Swaplace", async function () { .to.emit(Swaplace, "SwapCreated") .withArgs(await Swaplace.totalSwaps(), owner.address, zeroAddress); }); + + it("Should be able to {createSwap} with native ethers sent by the {owner}", async function () { + const bidingAddr = [MockERC20.address]; + const bidingAmountOrId = [50]; + + const askingAddr = [ + MockERC20.address, + MockERC20.address, + MockERC20.address, + ]; + const askingAmountOrId = [50, 100, 150]; + + const valueToSend: BigNumber = ethers.utils.parseEther("0.5"); + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + valueToSend.div(1e12), + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + await expect( + await Swaplace.connect(owner).createSwap(swap, { + value: valueToSend, + }), + ) + .to.emit(Swaplace, "SwapCreated") + .withArgs(await Swaplace.totalSwaps(), owner.address, zeroAddress); + }); }); context("Reverts when creating Swaps", () => { it("Should revert when {owner} is not {msg.sender}", async function () { const swap = await mockSwap(); - await expect(Swaplace.connect(allowed).createSwap(swap)) - .to.be.revertedWithCustomError(Swaplace, `InvalidAddress`) - .withArgs(allowed.address); + await expect( + Swaplace.connect(allowed).createSwap(swap), + ).to.be.revertedWithCustomError(Swaplace, `InvalidAddress`); + }); + + it("Should revert when the wrong amount of ethers are sent by the {owner}", async function () { + const bidingAddr = [MockERC20.address]; + const bidingAmountOrId = [50]; + + const askingAddr = [ + MockERC20.address, + MockERC20.address, + MockERC20.address, + ]; + const askingAmountOrId = [50, 100, 150]; + + const valueToSend: BigNumber = ethers.utils.parseEther("0.5"); + + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + valueToSend.div(1e12), + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + await expect( + Swaplace.connect(owner).createSwap(swap, { value: 69 }), + ).to.be.revertedWithCustomError(Swaplace, `InvalidValue`); + }); + + it("Should revert when the {owner} sends ethers while being the {recipient}", async function () { + const bidingAddr = [MockERC20.address]; + const bidingAmountOrId = [50]; + + const askingAddr = [ + MockERC20.address, + MockERC20.address, + MockERC20.address, + ]; + const askingAmountOrId = [50, 100, 150]; + + const valueToSend: BigNumber = ethers.utils.parseEther("0.5"); + + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 1, + valueToSend.div(1e12), + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + await expect( + Swaplace.connect(owner).createSwap(swap, { value: valueToSend }), + ).to.be.revertedWithCustomError(Swaplace, `InvalidValue`); }); }); }); @@ -241,8 +633,8 @@ describe("Swaplace", async function () { MockERC20 = await deploy("MockERC20", deployer); MockERC721 = await deploy("MockERC721", deployer); - await MockERC721.mintTo(owner.address, 1); - await MockERC20.mintTo(allowed.address, 1000); + await MockERC721.mint(owner.address, 1); + await MockERC20.mint(allowed.address, 1000); await MockERC721.connect(owner).approve(Swaplace.address, 1); await MockERC20.connect(allowed).approve(Swaplace.address, 1000); @@ -253,8 +645,13 @@ describe("Swaplace", async function () { const askingAddr = [MockERC20.address]; const askingAmountOrId = [1000]; - const currentTimestamp = (await blocktimestamp()) * 2; - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + 0, + ); swap = await composeSwap( owner.address, @@ -284,8 +681,8 @@ describe("Swaplace", async function () { }); it("Should be able to {acceptSwap} as N-N Swap", async function () { - await MockERC20.mintTo(owner.address, 500); - await MockERC721.mintTo(allowed.address, 5); + await MockERC20.mint(owner.address, 500); + await MockERC721.mint(allowed.address, 5); await MockERC20.connect(owner).approve(Swaplace.address, 500); await MockERC721.connect(allowed).approve(Swaplace.address, 5); @@ -321,16 +718,21 @@ describe("Swaplace", async function () { }); it("Should be able to {acceptSwap} as P2P Swap", async function () { - await MockERC20.mintTo(owner.address, 1000); - await MockERC721.mintTo(allowed.address, 10); + await MockERC20.mint(owner.address, 1000); + await MockERC721.mint(allowed.address, 10); await MockERC20.connect(owner).approve(Swaplace.address, 1000); await MockERC721.connect(allowed).approve(Swaplace.address, 10); const swap = await mockSwap(); - const [, expiry] = await Swaplace.parseData(swap.config); + const [, expiry, ,] = await Swaplace.decodeConfig(swap.config); - swap.config = await Swaplace.packData(allowed.address, expiry); + swap.config = await Swaplace.encodeConfig( + allowed.address, + expiry, + 0, + 0, + ); await expect(await Swaplace.connect(owner).createSwap(swap)) .to.emit(Swaplace, "SwapCreated") @@ -353,6 +755,135 @@ describe("Swaplace", async function () { allowed.address, ); }); + + it("Should be able to {acceptSwap} with native ethers sent by the {owner}", async function () { + await MockERC20.mint(owner.address, 1000); + await MockERC721.mint(allowed.address, 10); + + await MockERC20.connect(owner).approve(Swaplace.address, 1000); + await MockERC721.connect(allowed).approve(Swaplace.address, 10); + + const bidingAddr = [MockERC20.address]; + const bidingAmountOrId = [50]; + + const askingAddr = [ + MockERC20.address, + MockERC20.address, + MockERC20.address, + ]; + const askingAmountOrId = [50, 100, 150]; + const valueToSend: BigNumber = ethers.utils.parseEther("0.5"); + const expiry = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + allowed.address, + expiry, + 0, + valueToSend.div(1e12), + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + + const balanceBefore: BigNumber = await receiver.getBalance(); + const expectedBalance: BigNumber = balanceBefore.add(valueToSend); + + await expect( + await Swaplace.connect(owner).createSwap(swap, { + value: valueToSend, + }), + ) + .to.emit(Swaplace, "SwapCreated") + .withArgs( + await Swaplace.totalSwaps(), + owner.address, + allowed.address, + ); + + await expect( + await Swaplace.connect(allowed).acceptSwap( + await Swaplace.totalSwaps(), + receiver.address, + ), + ) + .to.emit(Swaplace, "SwapAccepted") + .withArgs( + await Swaplace.totalSwaps(), + owner.address, + allowed.address, + ); + + const balanceAfter: BigNumber = await receiver.getBalance(); + await expect(balanceAfter).to.be.equals(expectedBalance); + }); + + it("Should be able to {acceptSwap} with native ethers sent by the {acceptee}", async function () { + await MockERC20.mint(owner.address, 1000); + await MockERC721.mint(allowed.address, 10); + + await MockERC20.connect(owner).approve(Swaplace.address, 1000); + await MockERC721.connect(allowed).approve(Swaplace.address, 10); + + const bidingAddr = [MockERC20.address]; + const bidingAmountOrId = [50]; + + const askingAddr = [ + MockERC20.address, + MockERC20.address, + MockERC20.address, + ]; + const askingAmountOrId = [50, 100, 150]; + const valueToSend: BigNumber = ethers.utils.parseEther("0.5"); + const expiry = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + allowed.address, + expiry, + 1, + valueToSend.div(1e12), + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + + await expect(await Swaplace.connect(owner).createSwap(swap)) + .to.emit(Swaplace, "SwapCreated") + .withArgs( + await Swaplace.totalSwaps(), + owner.address, + allowed.address, + ); + + const balanceBefore: BigNumber = await owner.getBalance(); + const expectedBalance: BigNumber = balanceBefore.add(valueToSend); + + await expect( + Swaplace.connect(allowed).acceptSwap( + await Swaplace.totalSwaps(), + receiver.address, + { value: valueToSend }, + ), + ) + .to.emit(Swaplace, "SwapAccepted") + .withArgs( + await Swaplace.totalSwaps(), + owner.address, + allowed.address, + ); + + const balanceAfter: BigNumber = await owner.getBalance(); + await expect(balanceAfter).to.be.equals(expectedBalance); + }); }); context("Reverts when accepting Swaps", () => { @@ -377,26 +908,22 @@ describe("Swaplace", async function () { await Swaplace.totalSwaps(), receiver.address, ), - ) - .to.be.revertedWithCustomError(Swaplace, `InvalidExpiry`) - .withArgs(0); + ).to.be.revertedWithCustomError(Swaplace, `InvalidExpiry`); }); it("Should revert when {expiry} is smaller than {block.timestamp}", async function () { await Swaplace.connect(owner).createSwap(swap); - const [, expiry] = await Swaplace.parseData(swap.config); + const [, expiry, ,] = await Swaplace.decodeConfig(swap.config); - await network.provider.send("evm_increaseTime", [expiry * 2]); + await network.provider.send("evm_increaseTime", [2000000]); await expect( Swaplace.connect(owner).acceptSwap( await Swaplace.totalSwaps(), receiver.address, ), - ) - .to.be.revertedWithCustomError(Swaplace, `InvalidExpiry`) - .withArgs(expiry); + ).to.be.revertedWithCustomError(Swaplace, `InvalidExpiry`); }); it("Should revert when {allowance} is not provided", async function () { @@ -409,20 +936,25 @@ describe("Swaplace", async function () { await Swaplace.totalSwaps(), receiver.address, ), - ).to.be.revertedWith(`ERC721: caller is not token owner or approved`); + ).to.be.revertedWithCustomError(Swaplace, `InvalidCall`); }); it("Should revert when {acceptSwap} as not allowed to P2P Swap", async function () { - await MockERC20.mintTo(owner.address, 1000); - await MockERC721.mintTo(allowed.address, 10); + await MockERC20.mint(owner.address, 1000); + await MockERC721.mint(allowed.address, 10); await MockERC20.connect(owner).approve(Swaplace.address, 1000); await MockERC721.connect(allowed).approve(Swaplace.address, 10); const swap = await mockSwap(); - const [, expiry] = await Swaplace.parseData(swap.config); - swap.config = await Swaplace.packData(deployer.address, expiry); + const [, expiry, ,] = await Swaplace.decodeConfig(swap.config); + swap.config = await Swaplace.encodeConfig( + deployer.address, + expiry, + 0, + 0, + ); await expect(await Swaplace.connect(owner).createSwap(swap)) .to.emit(Swaplace, "SwapCreated") @@ -437,21 +969,70 @@ describe("Swaplace", async function () { await Swaplace.totalSwaps(), receiver.address, ), - ) - .to.be.revertedWithCustomError(Swaplace, "InvalidAddress") - .withArgs(allowed.address); + ).to.be.revertedWithCustomError(Swaplace, "InvalidAddress"); + }); + + it("Should revert when wrong amount of ethers are sent by the {acceptee}", async function () { + await MockERC20.mint(owner.address, 1000); + await MockERC721.mint(allowed.address, 10); + + await MockERC20.connect(owner).approve(Swaplace.address, 1000); + await MockERC721.connect(allowed).approve(Swaplace.address, 10); + + const bidingAddr = [MockERC20.address]; + const bidingAmountOrId = [50]; + + const askingAddr = [ + MockERC20.address, + MockERC20.address, + MockERC20.address, + ]; + const askingAmountOrId = [50, 100, 150]; + const valueToSend: BigNumber = ethers.utils.parseEther("0.5"); + const expiry = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + allowed.address, + expiry, + 1, + valueToSend.div(1e12), + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + + await expect(await Swaplace.connect(owner).createSwap(swap)) + .to.emit(Swaplace, "SwapCreated") + .withArgs( + await Swaplace.totalSwaps(), + owner.address, + allowed.address, + ); + + await expect( + Swaplace.connect(allowed).acceptSwap( + await Swaplace.totalSwaps(), + receiver.address, + { value: 69 }, + ), + ).to.be.revertedWithCustomError(Swaplace, "InvalidValue"); }); }); }); describe("Canceling Swaps", () => { - context("Canceling Swaps", () => { - var swap: Swap; - before(async () => { - swap = await mockSwap(); - await Swaplace.connect(owner).createSwap(swap); - }); + var swap: Swap; + beforeEach(async () => { + swap = await mockSwap(); + await Swaplace.connect(owner).createSwap(swap); + }); + context("Canceling Swaps", () => { it("Should be able to {cancelSwap} a Swap", async function () { const lastSwap = await Swaplace.totalSwaps(); await expect(await Swaplace.connect(owner).cancelSwap(lastSwap)) @@ -461,68 +1042,228 @@ describe("Swaplace", async function () { it("Should not be able to {acceptSwap} a canceled a Swap", async function () { const lastSwap = await Swaplace.totalSwaps(); + await Swaplace.connect(owner).acceptSwap(lastSwap, receiver.address), + await expect( + Swaplace.connect(owner).acceptSwap(lastSwap, receiver.address), + ).to.be.revertedWithCustomError(Swaplace, `InvalidExpiry`); + }); + + it("Should be able to {cancelSwap} and return ethers to {owner}", async function () { + const bidingAddr = [MockERC20.address]; + const bidingAmountOrId = [50]; + + const askingAddr = [ + MockERC20.address, + MockERC20.address, + MockERC20.address, + ]; + const askingAmountOrId = [50, 100, 150]; + + const valueToSend: BigNumber = ethers.utils.parseEther("0.5"); + + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + valueToSend.div(1e12), + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + await expect( - Swaplace.connect(owner).acceptSwap(lastSwap, receiver.address), + await Swaplace.connect(owner).createSwap(swap, { + value: valueToSend, + }), ) - .to.be.revertedWithCustomError(Swaplace, `InvalidExpiry`) - .withArgs(0); - }); - }); + .to.emit(Swaplace, "SwapCreated") + .withArgs(await Swaplace.totalSwaps(), owner.address, zeroAddress); - context("Reverts when canceling Swaps", () => { - var swap: Swap; - before(async () => { - swap = await mockSwap(); - await Swaplace.connect(owner).createSwap(swap); + const balanceBefore = await owner.getBalance(); + + const lastSwap = await Swaplace.totalSwaps(); + const tx = await Swaplace.connect(owner).cancelSwap(lastSwap); + const receipt = await tx.wait(); + const gasUsed = receipt.gasUsed; + const gasPrice = receipt.effectiveGasPrice; + + const balanceAfter = await owner.getBalance(); + expect(balanceBefore.add(valueToSend)).to.be.equals( + balanceAfter.add(gasPrice.mul(gasUsed)), + ); }); - it("Should revert when {owner} is not {msg.sender}", async function () { + it("Should be able to {cancelSwap} and return ethers to {owner} even after expiration", async function () { + const bidingAddr = [MockERC20.address]; + const bidingAmountOrId = [50]; + + const askingAddr = [ + MockERC20.address, + MockERC20.address, + MockERC20.address, + ]; + const askingAmountOrId = [50, 100, 150]; + + const valueToSend: BigNumber = ethers.utils.parseEther("0.5"); + + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 0, + valueToSend.div(1e12), + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); + + await expect( + await Swaplace.connect(owner).createSwap(swap, { + value: valueToSend, + }), + ) + .to.emit(Swaplace, "SwapCreated") + .withArgs(await Swaplace.totalSwaps(), owner.address, zeroAddress); + + await network.provider.send("evm_increaseTime", [1000000]); + + const balanceBefore = await owner.getBalance(); + const lastSwap = await Swaplace.totalSwaps(); - await expect(Swaplace.connect(allowed).cancelSwap(lastSwap)) - .to.be.revertedWithCustomError(Swaplace, `InvalidAddress`) - .withArgs(allowed.address); + const tx = await Swaplace.connect(owner).cancelSwap(lastSwap); + const receipt = await tx.wait(); + const gasUsed = receipt.gasUsed; + const gasPrice = receipt.effectiveGasPrice; + + const balanceAfter = await owner.getBalance(); + expect(balanceBefore.add(valueToSend)).to.be.equals( + balanceAfter.add(gasPrice.mul(gasUsed)), + ); }); - it("Should revert when {expiry} is smaller than {block.timestamp}", async function () { - const [, expiry] = await Swaplace.parseData(swap.config); + it("Should be able to {cancelSwap} before expiration if the recipient is the {owner}", async function () { + const bidingAddr = [MockERC20.address]; + const bidingAmountOrId = [50]; + + const askingAddr = [ + MockERC20.address, + MockERC20.address, + MockERC20.address, + ]; + const askingAmountOrId = [50, 100, 150]; + + const valueToSend: BigNumber = ethers.utils.parseEther("0.5"); + + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 1, + valueToSend.div(1e12), + ); + + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); - await network.provider.send("evm_increaseTime", [expiry * 2]); + await expect(await Swaplace.connect(owner).createSwap(swap)) + .to.emit(Swaplace, "SwapCreated") + .withArgs(await Swaplace.totalSwaps(), owner.address, zeroAddress); const lastSwap = await Swaplace.totalSwaps(); - await expect(Swaplace.connect(owner).cancelSwap(lastSwap)) - .to.be.revertedWithCustomError(Swaplace, `InvalidExpiry`) - .withArgs(expiry); + await expect(await Swaplace.connect(owner).cancelSwap(lastSwap)) + .to.emit(Swaplace, "SwapCanceled") + .withArgs(lastSwap, owner.address); + + await expect( + Swaplace.connect(owner).cancelSwap(lastSwap), + ).to.be.revertedWithCustomError(Swaplace, `InvalidExpiry`); }); - }); - }); - describe("Fetching Swaps", () => { - var swap: Swap; - before(async () => { - MockERC20 = await deploy("MockERC20", deployer); - MockERC721 = await deploy("MockERC721", deployer); + it("Should not be able to {cancelSwap} after expiration if the recipient is the {owner}", async function () { + const bidingAddr = [MockERC20.address]; + const bidingAmountOrId = [50]; - await MockERC721.mintTo(owner.address, 1); - await MockERC20.mintTo(allowed.address, 1000); + const askingAddr = [ + MockERC20.address, + MockERC20.address, + MockERC20.address, + ]; + const askingAmountOrId = [50, 100, 150]; - const bidingAddr = [MockERC721.address]; - const bidingAmountOrId = [1]; + const valueToSend: BigNumber = ethers.utils.parseEther("0.5"); - const askingAddr = [MockERC20.address]; - const askingAmountOrId = [1000]; + const currentTimestamp = (await blocktimestamp()) + 1000000; + const config = await Swaplace.encodeConfig( + zeroAddress, + currentTimestamp, + 1, + valueToSend.div(1e12), + ); - const currentTimestamp = (await blocktimestamp()) * 2; - const config = await Swaplace.packData(zeroAddress, currentTimestamp); + const swap: Swap = await composeSwap( + owner.address, + config, + bidingAddr, + bidingAmountOrId, + askingAddr, + askingAmountOrId, + ); - swap = await composeSwap( - owner.address, - config, - bidingAddr, - bidingAmountOrId, - askingAddr, - askingAmountOrId, - ); + await expect(await Swaplace.connect(owner).createSwap(swap)) + .to.emit(Swaplace, "SwapCreated") + .withArgs(await Swaplace.totalSwaps(), owner.address, zeroAddress); + + await network.provider.send("evm_increaseTime", [1000000]); + + const lastSwap = await Swaplace.totalSwaps(); + await expect( + Swaplace.connect(owner).cancelSwap(lastSwap), + ).to.be.revertedWithCustomError(Swaplace, `InvalidExpiry`); + }); + }); + + context("Reverts when canceling Swaps", () => { + it("Should revert when {owner} is not {msg.sender}", async function () { + const lastSwap = await Swaplace.totalSwaps(); + await expect( + Swaplace.connect(allowed).cancelSwap(lastSwap), + ).to.be.revertedWithCustomError(Swaplace, `InvalidAddress`); + }); + + it("Should revert when {expiry} is smaller than {block.timestamp}", async function () { + await network.provider.send("evm_increaseTime", [2000000]); + const lastSwap = await Swaplace.totalSwaps(); + await expect( + Swaplace.connect(owner).cancelSwap(lastSwap), + ).to.be.revertedWithCustomError(Swaplace, `InvalidExpiry`); + }); + }); + }); + + describe("Fetching Swaps", () => { + var swap: Swap; + beforeEach(async () => { + swap = await mockSwap(); await Swaplace.connect(owner).createSwap(swap); }); @@ -530,9 +1271,10 @@ describe("Swaplace", async function () { const lastSwap = await Swaplace.totalSwaps(); const fetchedSwap = await Swaplace.getSwap(lastSwap); + const [, expiry, ,] = await Swaplace.decodeConfig(swap.config); + expect(fetchedSwap.owner).not.to.be.equals(zeroAddress); - // swap.allowed can be the zero address and shoul not be trusted for validation - expect(fetchedSwap.expiry).not.to.be.equals(0); + expect(expiry).not.to.be.equals(0); expect(fetchedSwap.biding.length).to.be.greaterThan(0); expect(fetchedSwap.asking.length).to.be.greaterThan(0); }); @@ -542,7 +1284,7 @@ describe("Swaplace", async function () { const fetchedSwap = await Swaplace.getSwap(imaginarySwapId); // swap.allowed can be the zero address and shoul not be trusted for validation expect(fetchedSwap.owner).to.be.deep.equals(zeroAddress); - const [fetchedAllowed, fetchedExpiry] = await Swaplace.parseData( + const [fetchedAllowed, fetchedExpiry, ,] = await Swaplace.decodeConfig( fetchedSwap.config, ); expect(fetchedAllowed).to.be.deep.equals(zeroAddress); diff --git a/test/utils/SwapFactory.ts b/test/utils/SwapFactory.ts index 9879aa8..5f2c277 100644 --- a/test/utils/SwapFactory.ts +++ b/test/utils/SwapFactory.ts @@ -5,7 +5,7 @@ import { ethers } from "hardhat"; */ export interface Asset { addr: string; - amountOrId: bigint; + amountOrId: bigint | number; } /** @@ -13,17 +13,56 @@ export interface Asset { */ export interface Swap { owner: string; - config: number; + config: bigint; biding: Asset[]; asking: Asset[]; } +/** + * @dev See {ISwapFactory-encodeConfig}. + */ +export async function encodeConfig( + allowed: string, + expiry: bigint | number, + recipient: bigint | number, + value: bigint | number, +): Promise { + return ( + (BigInt(allowed) << BigInt(96)) | + (BigInt(expiry) << BigInt(64)) | + (BigInt(recipient) << BigInt(56)) | + BigInt(value) + ); +} + +/** + * @dev See {ISwapFactory-decodeConfig}. + */ +export async function decodeConfig(config: bigint): Promise<{ + allowed: string; + expiry: bigint | number; + recipient: bigint | number; + value: bigint | number; +}> { + return { + allowed: + config >> BigInt(96) == BigInt(0) + ? ethers.constants.AddressZero + : ethers.utils.getAddress( + `0x${(config >> BigInt(96)).toString(16).padStart(40, "0")}`, + ), + expiry: (config >> BigInt(64)) & ((BigInt(1) << BigInt(32)) - BigInt(1)), + recipient: (config >> BigInt(56)) & ((BigInt(1) << BigInt(8)) - BigInt(1)), + value: config & ((BigInt(1) << BigInt(56)) - BigInt(1)), + }; +} + /** * @dev See {ISwapFactory-makeAsset}. */ export async function makeAsset( addr: string, - amountOrId: number | bigint, + amountOrId: bigint | number, ): Promise { // validate if its an ethereum address if (!ethers.utils.isAddress(addr)) { @@ -43,18 +82,46 @@ export async function makeAsset( */ const asset: Asset = { addr: addr, - amountOrId: typeof amountOrId == "number" ? BigInt(amountOrId) : amountOrId, + amountOrId: amountOrId, }; return asset; } +/** + * @dev See {ISwapFactory-encodeAsset}. + */ +export async function encodeAsset( + tokenId: bigint | number, + tokenAmount: bigint | number, +): Promise { + // if the amount or ID is negative, it will throw an error + if (tokenId < 0 || tokenAmount < 0) { + throw new Error("tokenId or tokenAmount cannot be less than 0"); + } + + const uint16Max = 65535; + const uint120Max = BigInt(2) ** BigInt(120) - BigInt(1); + + if (tokenId > uint120Max || tokenAmount > uint120Max) { + throw new Error( + "Maxium bits exceeded for tokenId or tokenAmount. Max: 120 bits.", + ); + } + + return BigInt( + (BigInt(uint16Max) << BigInt(240)) | + (BigInt(tokenId) << BigInt(120)) | + BigInt(tokenAmount), + ); +} + /** * @dev See {ISwapFactory-makeSwap}. */ export async function makeSwap( - owner: any, - config: any, + owner: string, + config: bigint, biding: Asset[], asking: Asset[], ) { @@ -108,12 +175,12 @@ export async function makeSwap( * - `askingAddr` and `askingAmountOrId` must have the same length. */ export async function composeSwap( - owner: any, - config: any, - bidingAddr: any[], - bidingAmountOrId: any[], - askingAddr: any[], - askingAmountOrId: any[], + owner: string, + config: bigint, + bidingAddr: string[], + bidingAmountOrId: bigint[] | number[], + askingAddr: string[], + askingAmountOrId: bigint[] | number[], ) { // lenght of addresses and their respective amounts must be equal if ( @@ -124,12 +191,12 @@ export async function composeSwap( } // push new assets to the array of bids and asks - const biding: any[] = []; + const biding: Asset[] = []; bidingAddr.forEach(async (addr, index) => { biding.push(await makeAsset(addr, bidingAmountOrId[index])); }); - const asking: any[] = []; + const asking: Asset[] = []; askingAddr.forEach(async (addr, index) => { asking.push(await makeAsset(addr, askingAmountOrId[index])); }); @@ -141,4 +208,7 @@ module.exports = { makeAsset, makeSwap, composeSwap, + encodeConfig, + decodeConfig, + encodeAsset, }; diff --git a/test/utils/utils.ts b/test/utils/utils.ts index 679ccf8..6c217bd 100644 --- a/test/utils/utils.ts +++ b/test/utils/utils.ts @@ -1,4 +1,7 @@ import { ethers } from "hardhat"; +import fs from "fs"; +import path from "path"; +import { Contract, ContractFactory } from "ethers"; /** * @dev Get the current `block.timestamp` in seconds from the current @@ -18,19 +21,75 @@ export async function blocktimestamp(): Promise { * @param signer The signer to use. */ export async function deploy(contractName: any, signer: any) { - // Get Contract Factory for contractName - const ContractFactory = await ethers.getContractFactory(contractName, signer); + // @dev Setting variables to be used in the function. + let ContractFactory: ContractFactory; + let Contract: Contract; - // Deploy the Contract - const Contract = await ContractFactory.deploy(); + // @dev Get Contract Factory for contractName and reverts if not found. + try { + ContractFactory = await ethers.getContractFactory(contractName, signer); + } catch (error) { + throw new Error( + `Error getting the Contract Factory for ${contractName}. + Make sure the contract is compiled, the type-chain generated + and a valid Ethereum Address for signer set in hardhat.config.ts. + ${error}`, + ); + } - // Wait for Contract to be deployed + // @dev Deploy the Contract and reverts if the transaction fails. + try { + Contract = await ContractFactory.deploy(); + } catch (error) { + throw new Error( + `Error deploying the Contract ${contractName}. + Make sure the network is correct, that you have a valid Ethereum Address + for signer with enough funds for the transaction. The gas settings might + as well be lower than the amount required by the network at the moment. + ${error}`, + ); + } + + // @dev Wait for the deployment transaction to be mined in the blockchain. await Contract.deployed(); return Contract; } +export async function storeEnv( + contractAddress: any, + envVarName: string, + showLog: boolean, +) { + const filePath = path.join(__dirname, "../../.env"); // .env file path + + try { + /// @dev Read the file synchronously + let data = fs.readFileSync(filePath, "utf8"); + + // @dev Replace the contract address in the .env file + const updatedContent = data.replace( + new RegExp(`${envVarName}=.*`), + `${envVarName}=${contractAddress}`, + ); + + /// @dev Write the updated content to the file synchronously + fs.writeFileSync(filePath, updatedContent, "utf8"); + + if (showLog) { + console.log( + "Stored the data %s in .env file at the %s variable", + contractAddress, + envVarName, + ); + } + } catch (err) { + console.error("Error reading or writing file:", err); + } +} + module.exports = { blocktimestamp, + storeEnv, deploy, };