SwapPool.sol (8192B)
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 public tokenRegistry; 13 address public tokenLimiter; 14 address public quoter; 15 uint256 public feePpm; 16 address public 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 ) public 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 // Emitted after a successful swap 40 event Swap( 41 address indexed initiator, 42 address indexed tokenIn, 43 address tokenOut, 44 uint256 amountIn, 45 uint256 amountOut, 46 uint256 fee 47 ); 48 49 // Emitted only after a liquidity donation 50 event Deposit( 51 address indexed initiator, 52 address indexed tokenIn, 53 uint256 amountIn 54 ); 55 56 // Emitted when collecting fees to the set feeAddress 57 event Collect( 58 address indexed feeAddress, 59 address tokenOut, 60 uint256 amountOut 61 ); 62 63 constructor(string memory _name, string memory _symbol, uint8 _decimals, address _tokenRegistry, address _tokenLimiter) { 64 name = _name; 65 symbol = _symbol; 66 decimals = _decimals; 67 tokenRegistry = _tokenRegistry; 68 tokenLimiter = _tokenLimiter; 69 owner = msg.sender; 70 } 71 72 function seal(uint256 _state) public returns(uint256) { 73 require(_state <= maxSealState, 'ERR_INVALID_STATE'); 74 require(_state & sealState == 0, 'ERR_ALREADY_LOCKED'); 75 sealState |= _state; 76 emit SealStateChange(sealState == maxSealState, sealState); 77 return uint256(sealState); 78 } 79 80 function isSealed(uint256 _state) public view returns(bool) { 81 require(_state < maxSealState); 82 if (_state == 0) { 83 return sealState == maxSealState; 84 } 85 return _state & sealState == _state; 86 } 87 88 // Change address for collecting fees 89 function setFeeAddress(address _feeAddress) public { 90 require(!isSealed(FEEADDRESS_STATE), "ERR_SEAL"); 91 require(msg.sender == owner, "ERR_AXX"); 92 feeAddress = _feeAddress; 93 } 94 95 // Change address for collecting fees 96 function setFee(uint256 _fee) public { 97 require(!isSealed(FEE_STATE), "ERR_SEAL"); 98 require(msg.sender == owner, "ERR_AXX"); 99 require(_fee < 1000000, "ERR_FEE_TOO_HIGH"); 100 feePpm = _fee; 101 } 102 103 // Change address for the quoter contract 104 function setQuoter(address _quoter) public { 105 require(!isSealed(QUOTER_STATE), "ERR_SEAL"); 106 require(msg.sender == owner, "ERR_AXX"); 107 quoter = _quoter; 108 } 109 110 // Implements EIP173 111 function transferOwnership(address _newOwner) public returns (bool) { 112 address oldOwner; 113 114 require(msg.sender == owner); 115 oldOwner = owner; 116 owner = _newOwner; 117 118 emit OwnershipTransferred(oldOwner, owner); 119 return true; 120 } 121 122 function deposit(address _token, uint256 _value) public { 123 _deposit(_token, _value); 124 emit Deposit(msg.sender, _token, _value); 125 } 126 127 function _deposit(address _token, uint256 _value) private { 128 bool r; 129 bytes memory v; 130 131 mustAllowedToken(_token, tokenRegistry); 132 mustWithinLimit(_token, _value); 133 134 (r, v) = _token.call(abi.encodeWithSignature('transferFrom(address,address,uint256)', msg.sender, this, _value)); 135 require(r, "ERR_TOKEN"); 136 r = abi.decode(v, (bool)); 137 require(r, "ERR_TRANSFER"); 138 139 totalSupply += _value; 140 } 141 142 function getFee(uint256 _value) private view returns (uint256) { 143 uint256 fee; 144 145 fee = _value * feePpm; 146 fee /= 1000000; 147 148 return fee; 149 } 150 151 function getQuote(address _outToken, address _inToken, uint256 _value) public returns (uint256) { 152 bool r; 153 bytes memory v; 154 uint256 quote; 155 156 if (quoter == address(0x0)) { 157 return _value; 158 } 159 160 (r, v) = quoter.call(abi.encodeWithSignature('valueFor(address,address,uint256)', _outToken, _inToken, _value)); 161 require(r, "ERR_QUOTER"); 162 quote = abi.decode(v, (uint256)); 163 return quote; 164 } 165 166 function withdraw_less_fee(address _outToken, address _inToken, uint256 _value) public { 167 bool r; 168 bytes memory v; 169 uint256 balance; 170 uint256 fee; 171 uint256 outValue; 172 173 outValue = getQuote(_outToken, _inToken, _value); 174 175 (r, v) = _outToken.call(abi.encodeWithSignature("balanceOf(address)", this)); 176 require(r, "ERR_TOKEN"); 177 balance = abi.decode(v, (uint256)); 178 179 // deduct the fees from the quoted outValue 180 fee = getFee(outValue); 181 outValue -= fee; 182 183 // pool should have enough balance to cover the final outValue (fees already deducted) 184 require(balance >= outValue, "ERR_BALANCE"); 185 186 _deposit(_inToken, _value); 187 188 (r, v) = _outToken.call(abi.encodeWithSignature('transfer(address,uint256)', msg.sender, outValue)); 189 require(r, "ERR_TOKEN"); 190 r = abi.decode(v, (bool)); 191 require(r, "ERR_TRANSFER"); 192 193 if (feeAddress != address(0)) { 194 fees[_outToken] += fee; 195 } 196 197 emit Swap(msg.sender, _inToken, _outToken, _value, outValue, fee); 198 } 199 200 function withdraw(address _outToken, address _inToken, uint256 _value) public { 201 bool r; 202 bytes memory v; 203 uint256 netValue; 204 uint256 outValue; 205 uint256 balance; 206 uint256 fee; 207 208 fee = getFee(_value); 209 netValue = _value - fee; 210 netValue = getQuote(_outToken, _inToken, netValue); 211 212 (r, v) = _outToken.call(abi.encodeWithSignature("balanceOf(address)", this)); 213 require(r, "ERR_TOKEN"); 214 balance = abi.decode(v, (uint256)); 215 outValue = netValue + fee; 216 require(balance >= outValue, "ERR_BALANCE"); 217 218 deposit(_inToken, _value); 219 220 (r, v) = _outToken.call(abi.encodeWithSignature('transfer(address,uint256)', msg.sender, netValue)); 221 require(r, "ERR_TOKEN"); 222 r = abi.decode(v, (bool)); 223 require(r, "ERR_TRANSFER"); 224 225 if (feeAddress != address(0)) { 226 fees[_outToken] += fee; 227 } 228 229 emit Swap(msg.sender, _inToken, _outToken, _value, outValue, fee); 230 } 231 232 function withdraw(address _outToken, address _inToken, uint256 _value, bool _deduct_fee) public { 233 if (_deduct_fee) { 234 withdraw_less_fee(_outToken, _inToken, _value); 235 } else { 236 withdraw(_outToken, _inToken, _value); 237 } 238 } 239 240 // Withdraw token to fee address 241 function withdraw(address _outToken) public returns (uint256) { 242 uint256 balance; 243 244 balance = fees[_outToken]; 245 fees[_outToken] = 0; 246 247 return withdraw(_outToken, balance); 248 } 249 250 function withdraw(address _outToken, uint256 _value) public returns (uint256) { 251 bool r; 252 bytes memory v; 253 254 require(msg.sender == owner, "ERR_OWNER"); 255 require(feeAddress != address(0), "ERR_AXX"); 256 257 (r, v) = _outToken.call(abi.encodeWithSignature('transfer(address,uint256)', feeAddress, _value)); 258 require(r, "ERR_TOKEN"); 259 r = abi.decode(v, (bool)); 260 require(r, "ERR_TRANSFER"); 261 262 emit Collect(feeAddress, _outToken, _value); 263 return _value; 264 } 265 266 function mustAllowedToken(address _token, address _tokenRegistry) private { 267 bool r; 268 bytes memory v; 269 270 if (_tokenRegistry == address(0)) { 271 return; 272 } 273 274 (r, v) = _tokenRegistry.call(abi.encodeWithSignature('have(address)', _token)); 275 require(r, "ERR_REGISTRY"); 276 r = abi.decode(v, (bool)); 277 require(r, "ERR_UNAUTH_TOKEN"); 278 } 279 280 function mustWithinLimit(address _token, uint256 _valueDelta) private { 281 bool r; 282 bytes memory v; 283 uint256 limit; 284 uint256 balance; 285 286 if (tokenLimiter == address(0)) { 287 return; 288 } 289 290 291 (r, v) = tokenLimiter.call(abi.encodeWithSignature("limitOf(address,address)", _token, this)); 292 require(r, "ERR_LIMITER"); 293 limit = abi.decode(v, (uint256)); 294 295 (r, v) = _token.call(abi.encodeWithSignature("balanceOf(address)", this)); 296 require(r, "ERR_TOKEN"); 297 balance = abi.decode(v, (uint256)); 298 require(balance + _valueDelta <= limit, "ERR_LIMIT"); 299 } 300 301 // Implements EIP165 302 function supportsInterface(bytes4 _sum) public pure returns (bool) { 303 if (_sum == 0x01ffc9a7) { // ERC165 304 return true; 305 } 306 if (_sum == 0x9493f8b2) { // ERC173 307 return true; 308 } 309 if (_sum == 0x0d7491f8) { // Seal 310 return true; 311 } 312 return false; 313 } 314 } 315