SwapPool.sol (6169B)
1 pragma solidity ^0.8.0; 2 3 // Author: Louis Holbrook <dev@holbrook.no> 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 4 // SPDX-License-Identifier: AGPL-3.0-or-later 5 // File-Version: 1 6 // Description: ACL-enabled ERC20 token swap for tokens with compatible properties. 7 8 contract SwapPool { 9 // Implements EIP173 10 address public owner; 11 12 address tokenRegistry; 13 address tokenLimiter; 14 address quoter; 15 uint256 feePpm; 16 address feeAddress; 17 18 string public name; 19 string public symbol; 20 uint256 public immutable decimals; 21 22 uint256 public totalSupply; 23 24 mapping ( address => uint256 ) fees; 25 26 // Implements Seal 27 uint256 public sealState; 28 uint8 constant FEE_STATE = 1; 29 uint8 constant FEEADDRESS_STATE = 2; 30 uint8 constant QUOTER_STATE = 4; 31 uint256 constant public maxSealState = 7; 32 33 // Implements Seal 34 event SealStateChange(bool indexed _final, uint256 _sealState); 35 36 // EIP173 37 event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173 38 39 constructor(string memory _name, string memory _symbol, uint8 _decimals, address _tokenRegistry, address _tokenLimiter) { 40 name = _name; 41 symbol = _symbol; 42 decimals = _decimals; 43 tokenRegistry = _tokenRegistry; 44 tokenLimiter = _tokenLimiter; 45 owner = msg.sender; 46 } 47 48 function seal(uint256 _state) public returns(uint256) { 49 require(_state <= maxSealState, 'ERR_INVALID_STATE'); 50 require(_state & sealState == 0, 'ERR_ALREADY_LOCKED'); 51 sealState |= _state; 52 emit SealStateChange(sealState == maxSealState, sealState); 53 return uint256(sealState); 54 } 55 56 function isSealed(uint256 _state) public view returns(bool) { 57 require(_state < maxSealState); 58 if (_state == 0) { 59 return sealState == maxSealState; 60 } 61 return _state & sealState == _state; 62 } 63 64 // Change address for collecting fees 65 function setFeeAddress(address _feeAddress) public { 66 require(!isSealed(FEEADDRESS_STATE), "ERR_SEAL"); 67 require(msg.sender == owner, "ERR_AXX"); 68 feeAddress = _feeAddress; 69 } 70 71 // Change address for collecting fees 72 function setFee(uint256 _fee) public { 73 require(!isSealed(FEE_STATE), "ERR_SEAL"); 74 require(msg.sender == owner, "ERR_AXX"); 75 require(_fee < 1000000, "ERR_FEE_TOO_HIGH"); 76 feePpm = _fee; 77 } 78 79 // Change address for the quoter contract 80 function setQuoter(address _quoter) public { 81 require(!isSealed(QUOTER_STATE), "ERR_SEAL"); 82 require(msg.sender == owner, "ERR_AXX"); 83 quoter = _quoter; 84 } 85 86 // Implements EIP173 87 function transferOwnership(address _newOwner) public returns (bool) { 88 address oldOwner; 89 90 require(msg.sender == owner); 91 oldOwner = owner; 92 owner = _newOwner; 93 94 emit OwnershipTransferred(oldOwner, owner); 95 return true; 96 } 97 98 function deposit(address _token, uint256 _value) public { 99 bool r; 100 bytes memory v; 101 102 mustAllowedToken(_token, tokenRegistry); 103 mustWithinLimit(_token, _value); 104 105 (r, v) = _token.call(abi.encodeWithSignature('transferFrom(address,address,uint256)', msg.sender, this, _value)); 106 require(r, "ERR_TOKEN"); 107 r = abi.decode(v, (bool)); 108 require(r, "ERR_TRANSFER"); 109 110 totalSupply += _value; 111 } 112 113 function getFee(uint256 _value) private view returns (uint256) { 114 uint256 fee; 115 116 fee = _value * feePpm; 117 fee /= 1000000; 118 119 return fee; 120 } 121 122 function getQuote(address _outToken, address _inToken, uint256 _value) private returns (uint256) { 123 bool r; 124 bytes memory v; 125 uint256 quote; 126 127 if (quoter == address(0x0)) { 128 return _value; 129 } 130 131 (r, v) = quoter.call(abi.encodeWithSignature('valueFor(address,address,uint256)', _outToken, _inToken, _value)); 132 require(r, "ERR_QUOTER"); 133 quote = abi.decode(v, (uint256)); 134 return quote; 135 } 136 137 function withdraw(address _outToken, address _inToken, uint256 _value) public { 138 bool r; 139 bytes memory v; 140 uint256 netValue; 141 uint256 balance; 142 uint256 fee; 143 144 fee = getFee(_value); 145 netValue = _value - fee; 146 netValue = getQuote(_outToken, _inToken, netValue); 147 148 (r, v) = _outToken.call(abi.encodeWithSignature("balanceOf(address)", this)); 149 require(r, "ERR_TOKEN"); 150 balance = abi.decode(v, (uint256)); 151 require(balance >= netValue + fee, "ERR_BALANCE"); 152 153 deposit(_inToken, _value); 154 155 (r, v) = _outToken.call(abi.encodeWithSignature('transfer(address,uint256)', msg.sender, netValue)); 156 require(r, "ERR_TOKEN"); 157 r = abi.decode(v, (bool)); 158 require(r, "ERR_TRANSFER"); 159 160 if (feeAddress != address(0)) { 161 fees[_outToken] += fee; 162 } 163 } 164 165 // Withdraw token to fee address 166 function withdraw(address _outToken) public returns (uint256) { 167 uint256 balance; 168 169 balance = fees[_outToken]; 170 fees[_outToken] = 0; 171 172 return withdraw(_outToken, balance); 173 } 174 175 function withdraw(address _outToken, uint256 _value) public returns (uint256) { 176 bool r; 177 bytes memory v; 178 179 require(feeAddress != address(0), "ERR_AXX"); 180 require(_value <= fees[_outToken], "ERR_BALANCE"); 181 182 (r, v) = _outToken.call(abi.encodeWithSignature('transfer(address,uint256)', feeAddress, _value)); 183 require(r, "ERR_TOKEN"); 184 r = abi.decode(v, (bool)); 185 require(r, "ERR_TRANSFER"); 186 187 return _value; 188 } 189 190 function mustAllowedToken(address _token, address _tokenRegistry) private { 191 bool r; 192 bytes memory v; 193 194 if (_tokenRegistry == address(0)) { 195 return; 196 } 197 198 (r, v) = _tokenRegistry.call(abi.encodeWithSignature('have(address)', _token)); 199 require(r, "ERR_REGISTRY"); 200 r = abi.decode(v, (bool)); 201 require(r, "ERR_UNAUTH_TOKEN"); 202 } 203 204 function mustWithinLimit(address _token, uint256 _valueDelta) private { 205 bool r; 206 bytes memory v; 207 uint256 limit; 208 uint256 balance; 209 210 if (tokenLimiter == address(0)) { 211 return; 212 } 213 214 215 (r, v) = tokenLimiter.call(abi.encodeWithSignature("limitOf(address,address)", _token, this)); 216 require(r, "ERR_LIMITER"); 217 limit = abi.decode(v, (uint256)); 218 219 (r, v) = _token.call(abi.encodeWithSignature("balanceOf(address)", this)); 220 require(r, "ERR_TOKEN"); 221 balance = abi.decode(v, (uint256)); 222 require(balance + _valueDelta <= limit, "ERR_LIMIT"); 223 } 224 225 // Implements EIP165 226 function supportsInterface(bytes4 _sum) public pure returns (bool) { 227 if (_sum == 0x01ffc9a7) { // ERC165 228 return true; 229 } 230 if (_sum == 0x9493f8b2) { // ERC173 231 return true; 232 } 233 if (_sum == 0x0d7491f8) { // Seal 234 return true; 235 } 236 return false; 237 } 238 }