erc20-pool

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

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 }