pragma solidity ^0.8.0; // Author: Louis Holbrook 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 // SPDX-License-Identifier: AGPL-3.0-or-later // File-Version: 1 // Description: ACL-enabled ERC20 token swap for tokens with compatible properties. contract SwapPool { // Implements EIP173 address public owner; address tokenRegistry; address tokenLimiter; address quoter; uint256 feePpm; address feeAddress; bytes32 declaration; string public name; string public symbol; uint256 public immutable decimals; uint256 public totalSupply; mapping ( address => uint256 ) fees; // Implements Seal uint256 public sealState; uint8 constant FEE_STATE = 1; uint8 constant FEEADDRESS_STATE = 2; uint8 constant QUOTER_STATE = 2; uint256 constant public maxSealState = 3; // Implements Seal event SealStateChange(bool indexed _final, uint256 _sealState); // EIP173 event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173 constructor(string memory _name, string memory _symbol, uint8 _decimals, bytes32 _declaration, address _tokenRegistry, address _tokenLimiter) { name = _name; symbol = _symbol; decimals = _decimals; tokenRegistry = _tokenRegistry; tokenLimiter = _tokenLimiter; declaration = _declaration; owner = msg.sender; } function seal(uint256 _state) public returns(uint256) { require(_state <= maxSealState, 'ERR_INVALID_STATE'); require(_state & sealState == 0, 'ERR_ALREADY_LOCKED'); sealState |= _state; emit SealStateChange(sealState == maxSealState, sealState); return uint256(sealState); } function isSealed(uint256 _state) public view returns(bool) { require(_state < maxSealState); if (_state == 0) { return sealState == maxSealState; } return _state & sealState == _state; } // Change address for collecting fees function setFeeAddress(address _feeAddress) public { require(!isSealed(FEEADDRESS_STATE), "ERR_SEAL"); require(msg.sender == owner, "ERR_AXX"); feeAddress = _feeAddress; } // Change address for collecting fees function setFee(uint256 _fee) public { require(!isSealed(FEE_STATE), "ERR_SEAL"); require(msg.sender == owner, "ERR_AXX"); require(_fee < 1000000, "ERR_FEE_TOO_HIGH"); feePpm = _fee; } // Change address for the quoter contract function setQuoter(address _quoter) public { require(!isSealed(QUOTER_STATE), "ERR_SEAL"); require(msg.sender == owner, "ERR_AXX"); quoter = _quoter; } // Implements EIP173 function transferOwnership(address _newOwner) public returns (bool) { address oldOwner; require(msg.sender == owner); oldOwner = owner; owner = _newOwner; emit OwnershipTransferred(oldOwner, owner); return true; } function deposit(address _token, uint256 _value) public { bool r; bytes memory v; mustAllowedToken(_token, tokenRegistry); mustWithinLimit(_token, _value); (r, v) = _token.call(abi.encodeWithSignature('transferFrom(address,address,uint256)', msg.sender, this, _value)); require(r, "ERR_TOKEN"); r = abi.decode(v, (bool)); require(r, "ERR_TRANSFER"); totalSupply += _value; } function getFee(uint256 _value) private view returns (uint256) { uint256 fee; fee = _value * feePpm; fee /= 1000000; return fee; } function getQuote(address _outToken, address _inToken, uint256 _value) private returns (uint256) { bool r; bytes memory v; uint256 quote; if (quoter == address(0x0)) { return _value; } (r, v) = quoter.call(abi.encodeWithSignature('valueFor(address,address,uint256)', _outToken, _inToken, _value)); require(r, "ERR_QUOTER"); quote = abi.decode(v, (uint256)); return quote; } function withdraw(address _outToken, address _inToken, uint256 _value) public { bool r; bytes memory v; uint256 netValue; uint256 balance; uint256 fee; fee = getFee(_value); netValue = _value - fee; netValue = getQuote(_outToken, _inToken, netValue); (r, v) = _outToken.call(abi.encodeWithSignature("balanceOf(address)", this)); require(r, "ERR_TOKEN"); balance = abi.decode(v, (uint256)); require(balance >= netValue + fee, "ERR_BALANCE"); deposit(_inToken, _value); (r, v) = _outToken.call(abi.encodeWithSignature('transfer(address,uint256)', msg.sender, netValue)); require(r, "ERR_TOKEN"); r = abi.decode(v, (bool)); require(r, "ERR_TRANSFER"); if (feeAddress != address(0)) { fees[_outToken] += fee; } } // Withdraw token to fee address function withdraw(address _outToken) public returns (uint256) { uint256 balance; balance = fees[_outToken]; fees[_outToken] = 0; return withdraw(_outToken, balance); } function withdraw(address _outToken, uint256 _value) public returns (uint256) { bool r; bytes memory v; require(feeAddress != address(0), "ERR_AXX"); (r, v) = _outToken.call(abi.encodeWithSignature('transfer(address,uint256)', feeAddress, _value)); require(r, "ERR_TOKEN"); r = abi.decode(v, (bool)); require(r, "ERR_TRANSFER"); return _value; } function mustAllowedToken(address _token, address _tokenRegistry) private { bool r; bytes memory v; if (_tokenRegistry == address(0)) { return; } (r, v) = _tokenRegistry.call(abi.encodeWithSignature('have(address)', _token)); require(r, "ERR_REGISTRY"); r = abi.decode(v, (bool)); require(r, "ERR_UNAUTH_TOKEN"); } function mustWithinLimit(address _token, uint256 _valueDelta) private { bool r; bytes memory v; uint256 limit; uint256 balance; if (tokenLimiter == address(0)) { return; } (r, v) = tokenLimiter.call(abi.encodeWithSignature("limitOf(address,address)", _token, this)); require(r, "ERR_LIMITER"); limit = abi.decode(v, (uint256)); (r, v) = _token.call(abi.encodeWithSignature("balanceOf(address)", this)); require(r, "ERR_TOKEN"); balance = abi.decode(v, (uint256)); require(balance + _valueDelta <= limit, "ERR_LIMIT"); } // Implements EIP165 function supportsInterface(bytes4 _sum) public pure returns (bool) { if (_sum == 0x01ffc9a7) { // ERC165 return true; } if (_sum == 0x9493f8b2) { // ERC173 return true; } if (_sum == 0x0d7491f8) { // Seal return true; } return false; } }