thxprotocol / modules-solidity

Default Asset Pools are connected to an ERC20 contract. The pool is controlled by a permissioned (OAuth2.0) REST API which is responsible for paying the gas costs used to manage the pool. Access to the pool is managed with a flexible role-based access mechanism. Pools can hold various reward configurations for the connected ERC20 tokent contract and will manage the distribution of those token rewards with a withdrawal system. The poll system used to govern the reward configuration and withdrawals is optional.
4 stars 3 forks source link

Research token swap mechanics #46

Closed peterpolman closed 2 years ago

peterpolman commented 2 years ago

Usecases:

valeriagrazzini commented 2 years ago

@peterpolman

THE SWAP USE CASE:

the most popular dexes swap contracts use the uniswap contract as router to manage the liquidity pools. Also Quick swap which is based on Polygon is based on Uniswap Protocol. We could build up a prototype to test if that protocol works with our pools.

The NFT USE CASE:

The market places like for example the most popular Opensea, give to the customer the opportunity to list their NFT for the sell. There are many configuration that the customer can choose:

APIS

Besides the popular market place is also possible to build a custom market place. Depend on complexity, if we adopt the solution to use built up apis, there are some options: TATUM: (this is very popular) https://apidoc.tatum.io/tag/NFT-(ERC-721-or-compatible)#operation/NftTransferErc721 https://tatum.io/pricing

Moralis Web3 https://moralis.io/nft-api/ https://moralis.io/pricing/

QuickNode https://www.quicknode.com/nft-api https://www.quicknode.com/pricing

All those library support the most used blockchain, included polygon.

Regarding the openzeppelin part, yes there is the escrow contract but is very basic. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/escrow/Escrow.sol What would purpose for this? That I didn't get :)

peterpolman commented 2 years ago

@valeriagrazzini Functionality for those use cases is clear to me, so in general it was required to have more insight into how what actual code is implemented to support these functionalities and if we can learn a thing or two about that.

I have provided some feedback on this through Discord; please extend your findings over here and try to not spend too much time on it. I'd like to start development for this early next week.

valeriagrazzini commented 2 years ago

@peterpolman I have looked into the quick swap smart contracts repo. The contracts are the exact copy of the uniswap v2 contracts. So the swap mechanism is based on the liquidity pools amount transfer and balancing. The swap is executed by 3 principal functions:

function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external virtual override ensure(deadline) returns (uint[] memory amounts) { amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'); TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to); }

Which I suppose is the one that fits on our requirements. What is crucial in this function is that there is a first transfer of the swap amount to the liquidity of the first token in the path.

// SWAP // requires the initial amount to have already been sent to the first pair function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual { for (uint i; i < path.length - 1; i++) { (address input, address output) = (path[i], path[i + 1]); (address token0,) = UniswapV2Library.sortTokens(input, output); uint amountOut = amounts[i + 1]; (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0)); address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to; IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap( amount0Out, amount1Out, to, new bytes(0) ); } }

this is the function that prepares the and checks the amount of the swap to be passed to the core swap function.
What is difficult to me to understand is the value of the last argument (new bytes(0)) passed to the core swap function

function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
    require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
    (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
    require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');

    uint balance0;
    uint balance1;
    { // scope for _token{0,1}, avoids stack too deep errors
    address _token0 = token0;
    address _token1 = token1;
    require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
    if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
    if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
    if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
    balance0 = IERC20(_token0).balanceOf(address(this));
    balance1 = IERC20(_token1).balanceOf(address(this));
    }
    uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
    uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
    require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
    { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
    uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
    uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
    require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
    }

    _update(balance0, balance1, _reserve0, _reserve1);
    emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}

this is the function when the magic happens:
the expected output amount is transferred to the sender ad there is an adjustment of the pool balances, also the is a verification at the and to maintain the correct ratio  in the pool