erc20-pool/solidity/SwapPool.sol

316 lines
8.0 KiB
Solidity
Raw Normal View History

2023-07-25 10:49:50 +02:00
pragma solidity ^0.8.0;
// Author: Louis Holbrook <dev@holbrook.no> 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
// SPDX-License-Identifier: AGPL-3.0-or-later
// File-Version: 1
2023-07-28 14:46:15 +02:00
// Description: ACL-enabled ERC20 token swap for tokens with compatible properties.
2023-07-25 10:49:50 +02:00
contract SwapPool {
// Implements EIP173
address public owner;
address public tokenRegistry;
address public tokenLimiter;
address public quoter;
uint256 public feePpm;
address public feeAddress;
string public name;
string public symbol;
uint256 public immutable decimals;
2023-07-25 10:49:50 +02:00
uint256 public totalSupply;
2024-04-09 09:46:31 +02:00
mapping ( address => uint256 ) public fees;
// Implements Seal
uint256 public sealState;
uint8 constant FEE_STATE = 1;
uint8 constant FEEADDRESS_STATE = 2;
2023-08-01 16:34:52 +02:00
uint8 constant QUOTER_STATE = 4;
uint256 constant public maxSealState = 7;
// Implements Seal
event SealStateChange(bool indexed _final, uint256 _sealState);
// EIP173
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173
2024-04-08 06:54:09 +02:00
// Emitted after a successful swap
2024-04-08 06:33:34 +02:00
event Swap(
address indexed initiator,
address indexed tokenIn,
address tokenOut,
uint256 amountIn,
uint256 amountOut,
uint256 fee
2024-04-08 06:33:34 +02:00
);
2024-04-08 06:54:09 +02:00
// Emitted only after a liquidity donation
event Deposit(
2024-04-08 06:54:09 +02:00
address indexed initiator,
address indexed tokenIn,
uint256 amountIn
);
2024-04-08 06:54:09 +02:00
// Emitted when collecting fees to the set feeAddress
event Collect(
2024-04-08 06:54:09 +02:00
address indexed feeAddress,
address tokenOut,
uint256 amountOut
);
constructor(string memory _name, string memory _symbol, uint8 _decimals, address _tokenRegistry, address _tokenLimiter) {
name = _name;
symbol = _symbol;
decimals = _decimals;
2023-07-27 18:31:13 +02:00
tokenRegistry = _tokenRegistry;
2023-07-28 14:46:15 +02:00
tokenLimiter = _tokenLimiter;
2023-07-27 08:45:15 +02:00
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;
}
2023-08-01 16:33:38 +02:00
// 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;
2023-07-25 10:49:50 +02:00
}
function deposit(address _token, uint256 _value) public {
_deposit(_token, _value);
emit Deposit(msg.sender, _token, _value);
}
function _deposit(address _token, uint256 _value) private {
2023-07-25 10:49:50 +02:00
bool r;
bytes memory v;
2023-07-27 18:31:13 +02:00
mustAllowedToken(_token, tokenRegistry);
mustWithinLimit(_token, _value);
2023-07-25 10:49:50 +02:00
(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;
2023-07-25 10:49:50 +02:00
}
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) public returns (uint256) {
2023-07-25 10:49:50 +02:00
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_less_fee(address _outToken, address _inToken, uint256 _value) public {
2023-07-25 10:49:50 +02:00
bool r;
bytes memory v;
uint256 balance;
uint256 fee;
uint256 outValue;
2023-07-25 10:49:50 +02:00
outValue = getQuote(_outToken, _inToken, _value);
2023-07-25 10:49:50 +02:00
(r, v) = _outToken.call(abi.encodeWithSignature("balanceOf(address)", this));
require(r, "ERR_TOKEN");
balance = abi.decode(v, (uint256));
// deduct the fees from the quoted outValue
fee = getFee(outValue);
outValue -= fee;
// pool should have enough balance to cover the final outValue (fees already deducted)
require(balance >= outValue, "ERR_BALANCE");
2023-07-25 10:49:50 +02:00
_deposit(_inToken, _value);
2023-07-25 10:49:50 +02:00
(r, v) = _outToken.call(abi.encodeWithSignature('transfer(address,uint256)', msg.sender, outValue));
2023-07-25 10:49:50 +02:00
require(r, "ERR_TOKEN");
r = abi.decode(v, (bool));
require(r, "ERR_TRANSFER");
if (feeAddress != address(0)) {
fees[_outToken] += fee;
}
2024-04-08 06:33:34 +02:00
emit Swap(msg.sender, _inToken, _outToken, _value, outValue, fee);
2023-07-25 10:49:50 +02:00
}
function withdraw(address _outToken, address _inToken, uint256 _value) public {
bool r;
bytes memory v;
uint256 netValue;
uint256 outValue;
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));
outValue = netValue + fee;
require(balance >= outValue, "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;
}
emit Swap(msg.sender, _inToken, _outToken, _value, outValue, fee);
}
function withdraw(address _outToken, address _inToken, uint256 _value, bool _deduct_fee) public {
if (_deduct_fee) {
withdraw_less_fee(_outToken, _inToken, _value);
} else {
withdraw(_outToken, _inToken, _value);
}
}
// Withdraw token to fee address
function withdraw(address _outToken) public returns (uint256) {
uint256 balance;
2023-07-25 10:49:50 +02:00
balance = fees[_outToken];
2023-07-27 08:45:15 +02:00
fees[_outToken] = 0;
return withdraw(_outToken, balance);
2023-07-25 10:49:50 +02:00
}
function withdraw(address _outToken, uint256 _value) public returns (uint256) {
bool r;
bytes memory v;
2024-04-09 09:46:31 +02:00
require(msg.sender == owner, "ERR_OWNER");
require(feeAddress != address(0), "ERR_AXX");
2023-07-27 08:45:15 +02:00
(r, v) = _outToken.call(abi.encodeWithSignature('transfer(address,uint256)', feeAddress, _value));
require(r, "ERR_TOKEN");
r = abi.decode(v, (bool));
require(r, "ERR_TRANSFER");
emit Collect(feeAddress, _outToken, _value);
return _value;
}
2023-07-27 18:31:13 +02:00
function mustAllowedToken(address _token, address _tokenRegistry) private {
2023-07-25 10:49:50 +02:00
bool r;
bytes memory v;
2023-07-27 18:31:13 +02:00
if (_tokenRegistry == address(0)) {
2023-07-25 10:49:50 +02:00
return;
}
2023-07-27 18:31:13 +02:00
(r, v) = _tokenRegistry.call(abi.encodeWithSignature('have(address)', _token));
2023-07-25 10:49:50 +02:00
require(r, "ERR_REGISTRY");
r = abi.decode(v, (bool));
require(r, "ERR_UNAUTH_TOKEN");
}
2023-07-27 18:31:13 +02:00
function mustWithinLimit(address _token, uint256 _valueDelta) private {
bool r;
bytes memory v;
uint256 limit;
uint256 balance;
2023-07-28 14:46:15 +02:00
if (tokenLimiter == address(0)) {
2023-07-27 18:31:13 +02:00
return;
}
2023-07-28 14:46:15 +02:00
(r, v) = tokenLimiter.call(abi.encodeWithSignature("limitOf(address,address)", _token, this));
require(r, "ERR_LIMITER");
2023-07-27 18:31:13 +02:00
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;
}
2023-07-25 10:49:50 +02:00
}