erc20-pool

Permissioned ERC20 swap pool for EVM
Info | Log | Files | Refs | README

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