From 7ca4aa363296ee6d2d8a02af29a4947edc2bde57 Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Sun, 22 Oct 2023 14:06:39 +0300 Subject: [PATCH 01/12] initial commit --- contracts/airswap/AirSwapFeeConnector.sol | 101 +++++++++++++++++++++ contracts/airswap/FeeLogic.sol | 40 ++++++++ contracts/airswap/FeeVault.sol | 13 +++ contracts/airswap/IAirSwapFeeConnector.sol | 20 ++++ contracts/airswap/IFeeLogic.sol | 10 ++ contracts/airswap/IFeeVault.sol | 6 ++ contracts/airswap/ISwapERC20.sol | 18 ++++ 7 files changed, 208 insertions(+) create mode 100644 contracts/airswap/AirSwapFeeConnector.sol create mode 100644 contracts/airswap/FeeLogic.sol create mode 100644 contracts/airswap/FeeVault.sol create mode 100644 contracts/airswap/IAirSwapFeeConnector.sol create mode 100644 contracts/airswap/IFeeLogic.sol create mode 100644 contracts/airswap/IFeeVault.sol create mode 100644 contracts/airswap/ISwapERC20.sol diff --git a/contracts/airswap/AirSwapFeeConnector.sol b/contracts/airswap/AirSwapFeeConnector.sol new file mode 100644 index 000000000..4417af91d --- /dev/null +++ b/contracts/airswap/AirSwapFeeConnector.sol @@ -0,0 +1,101 @@ +pragma solidity 0.5.17; + +import "../openzeppelin/SafeMath.sol"; +import "../openzeppelin/Ownable.sol"; +import "../openzeppelin/IERC20_.sol"; + +import "./IAirSwapFeeConnector.sol"; +import "./IFeeLogic.sol"; +import "./IFeeVault.sol"; +import "./ISwapERC20.sol"; + +interface AirSwapFeeConnector is Ownable, IAirSwapFeeConnector { + + using SafeMath for uint256; + + address public feeLogicAddress; + address public feeVaultAddress; + address public swapERC20Address; + + event FeeLogicAddressChangedEvent(address indexed sender, address newAddress); + event FeeVaultAddressChangedEvent(address indexed sender, address newAddress); + event SwapERC20AddressChangedEvent(address indexed sender, address newAddress); + + event SwapEvent( + address indexed sender, + address indexed recipient, + address sendToken, + uint256 sendAmount, + uint256 inputFee, + address receiveToken, + uint256 receiveAmount, + uint256 outputFee); + + /*** setters ***/ + + function setFeeVaultAddress(uint256 _newAddress) public onlyOwner { + feeVaultAddress = _newAddress; + emit FeeVaultAddressChangedEvent(msg.sender, feeVaultAddress); + } + + function setFeeLogicAddress(uint256 _newAddress) public onlyOwner { + feeLogicAddress = _newAddress; + emit FeeLogicAddressChangedEvent(msg.sender, feeLogicAddress); + } + + function setSwapERC20Address(uint256 _newAddress) public onlyOwner { + swapERC20Address = _newAddress; + emit SwapERC20AddressChangedEvent(msg.sender, swapERC20Address); + } + + function swap( + address _senderToken, + uint256 _totalSenderAmount, + address _signerWallet, + address _signerToken, + uint256 _signerAmount, + address _recipient, + uint256 _nonce, + uint256 _expiry, + uint8 _v, + bytes32 _r, + bytes32 _s) public { + + address sender = msg.sender; + + // first we move all the funds here + + require(IERC20_(_senderToken).transferFrom(sender, address(this), _totalSenderAmount), "transfer failed"); + + // then we collect the input fee + uint256 inputFee = IFeeLogic(feeLogicAddress).calculateInputFee(_totalSenderAmount); + require(IERC20_(_senderToken).transferFrom(address(this), feeVaultAddress, inputFee), "transfer failed"); + + uint256 senderAmountAfterFee = _totalSenderAmount.sub(inputFee); + + // now we do the swap + ISwapERC20(swapERC20Address).swap( + address(this), + _nonce, + _expiry, + _signerWallet, + _signerToken, + _signerAmount, + _senderToken, + sendAmountAfterFee, + _v, + _r, + _s); + + // now we collect the output fee + uint256 outputFee = IFeeLogic(feeLogicAddress).calculateOutputFee(_signerAmount); + require(IERC20_(_signerToken).transferFrom(address(this), feeVaultAddress, outputFee), "transfer failed"); + + uint256 receiveAmountAfterFee = _signerAmount.sub(outputFee); + + // now we send the user her due + require(IERC20_(_signerToken).transferFrom(address(this), _recipient, receiveAmountAfterFee), "transfer failed"); + + emit SwapEvent(sender, _recipient, _senderToken, _totalSenderAmount, inputFee, _signerToken, _signerAmount, outputFee); + } +} diff --git a/contracts/airswap/FeeLogic.sol b/contracts/airswap/FeeLogic.sol new file mode 100644 index 000000000..32dc12eab --- /dev/null +++ b/contracts/airswap/FeeLogic.sol @@ -0,0 +1,40 @@ +pragma solidity 0.5.17; + +import "../openzeppelin/SafeMath.sol"; +import "../openzeppelin/Ownable.sol"; +import "../openzeppelin/IERC20_.sol"; + +import "./IFeeLogic.sol"; + +contract FeeLogic is Ownable, IFeeLogic { + + using SafeMath for uint256; + + uint256 public constant POINTS = 1000; + + uint256 public inputFeeInPoints; + uint256 public outputFeeInPoints; + + event InputFeeChanged(address indexed sender, uint256 feeInPoints); + event OutputFeeChanged(address indexed sender, uint256 feeInPoints); + + /*** setters ***/ + + function setInputFee(uint256 _inputFeeInPoints) public onlyOwner { + inputFeeInPoints = _inputFeeInPoints; + emit InputFeeChanged(msg.sender, inputFeeInPoints); + } + + function setOutputFee(uint256 _outputFeeInPoints) public onlyOwner { + outputFeeInPoints = _outputFeeInPoints; + emit OutputFeeChanged(msg.sender, outputFeeInPoints); + } + + function calculateInputFee(uint256 _sendAmount) public returns (uint256) { + return _sendAmount.mul(inputFeeInPoints).div(POINTS); + } + + function calculateOutputFee(uint256 _receiveAmount) public returns (uint256) { + return _receiveAmount.mul(outputFeeInPoints).div(POINTS); + } +} diff --git a/contracts/airswap/FeeVault.sol b/contracts/airswap/FeeVault.sol new file mode 100644 index 000000000..e7a160441 --- /dev/null +++ b/contracts/airswap/FeeVault.sol @@ -0,0 +1,13 @@ +pragma solidity 0.5.17; + +import "../openzeppelin/Ownable.sol"; +import "../openzeppelin/IERC20_.sol"; + +import "./IFeeVault.sol"; + +contract FeeVault is Ownable, IFeeVault { + + function sendFunds(address _token, address _recipient, uint256 _amount) public onlyOwner returns (bool) { + return IERC20_(_token).transfer(_recipient, _amount); + } +} diff --git a/contracts/airswap/IAirSwapFeeConnector.sol b/contracts/airswap/IAirSwapFeeConnector.sol new file mode 100644 index 000000000..0efc9d235 --- /dev/null +++ b/contracts/airswap/IAirSwapFeeConnector.sol @@ -0,0 +1,20 @@ +pragma solidity 0.5.17; + +interface IAirSwapFeeConnector { + + function setFeeLogic(address feeLogicAddress) external; + function setFeeVault(address feeVaultAddress) external; + + function swap( + address senderToken, + uint256 totalSenderAmount, + address signerWallet, + address signerToken, + uint256 signerAmount, + address recipient, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s) external; +} diff --git a/contracts/airswap/IFeeLogic.sol b/contracts/airswap/IFeeLogic.sol new file mode 100644 index 000000000..9c1fdfd2e --- /dev/null +++ b/contracts/airswap/IFeeLogic.sol @@ -0,0 +1,10 @@ +pragma solidity 0.5.17; + +interface IFeeLogic { + + function setInputFee(uint256 _inputFeeInPoints) external; + function setOutputFee(uint256 _outputFeeInPoints) external; + + function calculateInputFee(uint256 sendAmount) external returns (uint256); + function calculateOutputFee(uint256 receiveAmount) external returns (uint256); +} diff --git a/contracts/airswap/IFeeVault.sol b/contracts/airswap/IFeeVault.sol new file mode 100644 index 000000000..08542ea59 --- /dev/null +++ b/contracts/airswap/IFeeVault.sol @@ -0,0 +1,6 @@ +pragma solidity 0.5.17; + +interface IFeeVault { + + function sendFunds(address token, address recipient, uint256 amount) external returns (bool); +} diff --git a/contracts/airswap/ISwapERC20.sol b/contracts/airswap/ISwapERC20.sol new file mode 100644 index 000000000..60111fc7e --- /dev/null +++ b/contracts/airswap/ISwapERC20.sol @@ -0,0 +1,18 @@ +pragma solidity 0.5.17; + +interface ISwapERC20 { + + function swap( + address recipient, + uint256 nonce, + uint256 expiry, + address signerWallet, + address signerToken, + uint256 signerAmount, + address senderToken, + uint256 senderAmount, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} From 1193e106750737364e5ed0586435e75390c230e1 Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Sun, 22 Oct 2023 14:08:50 +0300 Subject: [PATCH 02/12] fix --- contracts/airswap/AirSwapFeeConnector.sol | 80 ++++++++++++++-------- contracts/airswap/FeeLogic.sol | 1 - contracts/airswap/FeeVault.sol | 7 +- contracts/airswap/IAirSwapFeeConnector.sol | 5 +- contracts/airswap/IFeeLogic.sol | 3 +- contracts/airswap/IFeeVault.sol | 7 +- contracts/airswap/ISwapERC20.sol | 25 ++++--- 7 files changed, 78 insertions(+), 50 deletions(-) diff --git a/contracts/airswap/AirSwapFeeConnector.sol b/contracts/airswap/AirSwapFeeConnector.sol index 4417af91d..b1870588a 100644 --- a/contracts/airswap/AirSwapFeeConnector.sol +++ b/contracts/airswap/AirSwapFeeConnector.sol @@ -9,8 +9,7 @@ import "./IFeeLogic.sol"; import "./IFeeVault.sol"; import "./ISwapERC20.sol"; -interface AirSwapFeeConnector is Ownable, IAirSwapFeeConnector { - +contract AirSwapFeeConnector is Ownable, IAirSwapFeeConnector { using SafeMath for uint256; address public feeLogicAddress; @@ -24,26 +23,27 @@ interface AirSwapFeeConnector is Ownable, IAirSwapFeeConnector { event SwapEvent( address indexed sender, address indexed recipient, - address sendToken, - uint256 sendAmount, - uint256 inputFee, - address receiveToken, + address sendToken, + uint256 sendAmount, + uint256 inputFee, + address receiveToken, uint256 receiveAmount, - uint256 outputFee); + uint256 outputFee + ); /*** setters ***/ - function setFeeVaultAddress(uint256 _newAddress) public onlyOwner { + function setFeeVaultAddress(address _newAddress) public onlyOwner { feeVaultAddress = _newAddress; emit FeeVaultAddressChangedEvent(msg.sender, feeVaultAddress); } - function setFeeLogicAddress(uint256 _newAddress) public onlyOwner { + function setFeeLogicAddress(address _newAddress) public onlyOwner { feeLogicAddress = _newAddress; emit FeeLogicAddressChangedEvent(msg.sender, feeLogicAddress); } - function setSwapERC20Address(uint256 _newAddress) public onlyOwner { + function setSwapERC20Address(address _newAddress) public onlyOwner { swapERC20Address = _newAddress; emit SwapERC20AddressChangedEvent(msg.sender, swapERC20Address); } @@ -59,43 +59,65 @@ interface AirSwapFeeConnector is Ownable, IAirSwapFeeConnector { uint256 _expiry, uint8 _v, bytes32 _r, - bytes32 _s) public { - + bytes32 _s + ) public { address sender = msg.sender; // first we move all the funds here - require(IERC20_(_senderToken).transferFrom(sender, address(this), _totalSenderAmount), "transfer failed"); + require( + IERC20_(_senderToken).transferFrom(sender, address(this), _totalSenderAmount), + "transfer failed" + ); // then we collect the input fee uint256 inputFee = IFeeLogic(feeLogicAddress).calculateInputFee(_totalSenderAmount); - require(IERC20_(_senderToken).transferFrom(address(this), feeVaultAddress, inputFee), "transfer failed"); + require( + IERC20_(_senderToken).transferFrom(address(this), feeVaultAddress, inputFee), + "transfer failed" + ); uint256 senderAmountAfterFee = _totalSenderAmount.sub(inputFee); - + // now we do the swap ISwapERC20(swapERC20Address).swap( address(this), - _nonce, - _expiry, - _signerWallet, - _signerToken, - _signerAmount, - _senderToken, - sendAmountAfterFee, - _v, - _r, - _s); + _nonce, + _expiry, + _signerWallet, + _signerToken, + _signerAmount, + _senderToken, + senderAmountAfterFee, + _v, + _r, + _s + ); // now we collect the output fee uint256 outputFee = IFeeLogic(feeLogicAddress).calculateOutputFee(_signerAmount); - require(IERC20_(_signerToken).transferFrom(address(this), feeVaultAddress, outputFee), "transfer failed"); + require( + IERC20_(_signerToken).transferFrom(address(this), feeVaultAddress, outputFee), + "transfer failed" + ); uint256 receiveAmountAfterFee = _signerAmount.sub(outputFee); // now we send the user her due - require(IERC20_(_signerToken).transferFrom(address(this), _recipient, receiveAmountAfterFee), "transfer failed"); - - emit SwapEvent(sender, _recipient, _senderToken, _totalSenderAmount, inputFee, _signerToken, _signerAmount, outputFee); + require( + IERC20_(_signerToken).transferFrom(address(this), _recipient, receiveAmountAfterFee), + "transfer failed" + ); + + emit SwapEvent( + sender, + _recipient, + _senderToken, + _totalSenderAmount, + inputFee, + _signerToken, + _signerAmount, + outputFee + ); } } diff --git a/contracts/airswap/FeeLogic.sol b/contracts/airswap/FeeLogic.sol index 32dc12eab..c5e431a32 100644 --- a/contracts/airswap/FeeLogic.sol +++ b/contracts/airswap/FeeLogic.sol @@ -7,7 +7,6 @@ import "../openzeppelin/IERC20_.sol"; import "./IFeeLogic.sol"; contract FeeLogic is Ownable, IFeeLogic { - using SafeMath for uint256; uint256 public constant POINTS = 1000; diff --git a/contracts/airswap/FeeVault.sol b/contracts/airswap/FeeVault.sol index e7a160441..3e04a182a 100644 --- a/contracts/airswap/FeeVault.sol +++ b/contracts/airswap/FeeVault.sol @@ -6,8 +6,11 @@ import "../openzeppelin/IERC20_.sol"; import "./IFeeVault.sol"; contract FeeVault is Ownable, IFeeVault { - - function sendFunds(address _token, address _recipient, uint256 _amount) public onlyOwner returns (bool) { + function sendFunds( + address _token, + address _recipient, + uint256 _amount + ) public onlyOwner returns (bool) { return IERC20_(_token).transfer(_recipient, _amount); } } diff --git a/contracts/airswap/IAirSwapFeeConnector.sol b/contracts/airswap/IAirSwapFeeConnector.sol index 0efc9d235..2ca58b7ea 100644 --- a/contracts/airswap/IAirSwapFeeConnector.sol +++ b/contracts/airswap/IAirSwapFeeConnector.sol @@ -1,8 +1,8 @@ pragma solidity 0.5.17; interface IAirSwapFeeConnector { - function setFeeLogic(address feeLogicAddress) external; + function setFeeVault(address feeVaultAddress) external; function swap( @@ -16,5 +16,6 @@ interface IAirSwapFeeConnector { uint256 expiry, uint8 v, bytes32 r, - bytes32 s) external; + bytes32 s + ) external; } diff --git a/contracts/airswap/IFeeLogic.sol b/contracts/airswap/IFeeLogic.sol index 9c1fdfd2e..646607139 100644 --- a/contracts/airswap/IFeeLogic.sol +++ b/contracts/airswap/IFeeLogic.sol @@ -1,10 +1,11 @@ pragma solidity 0.5.17; interface IFeeLogic { - function setInputFee(uint256 _inputFeeInPoints) external; + function setOutputFee(uint256 _outputFeeInPoints) external; function calculateInputFee(uint256 sendAmount) external returns (uint256); + function calculateOutputFee(uint256 receiveAmount) external returns (uint256); } diff --git a/contracts/airswap/IFeeVault.sol b/contracts/airswap/IFeeVault.sol index 08542ea59..ed1282fee 100644 --- a/contracts/airswap/IFeeVault.sol +++ b/contracts/airswap/IFeeVault.sol @@ -1,6 +1,9 @@ pragma solidity 0.5.17; interface IFeeVault { - - function sendFunds(address token, address recipient, uint256 amount) external returns (bool); + function sendFunds( + address token, + address recipient, + uint256 amount + ) external returns (bool); } diff --git a/contracts/airswap/ISwapERC20.sol b/contracts/airswap/ISwapERC20.sol index 60111fc7e..3372f8127 100644 --- a/contracts/airswap/ISwapERC20.sol +++ b/contracts/airswap/ISwapERC20.sol @@ -1,18 +1,17 @@ pragma solidity 0.5.17; interface ISwapERC20 { - function swap( - address recipient, - uint256 nonce, - uint256 expiry, - address signerWallet, - address signerToken, - uint256 signerAmount, - address senderToken, - uint256 senderAmount, - uint8 v, - bytes32 r, - bytes32 s - ) external; + address recipient, + uint256 nonce, + uint256 expiry, + address signerWallet, + address signerToken, + uint256 signerAmount, + address senderToken, + uint256 senderAmount, + uint8 v, + bytes32 r, + bytes32 s + ) external; } From 19bcba55bd3a691111ac22338be1d5a66809a161 Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Sun, 22 Oct 2023 14:42:43 +0300 Subject: [PATCH 03/12] no need for separate fee logic --- contracts/airswap/AirSwapFeeConnector.sol | 50 ++++++++++++++++------- contracts/airswap/FeeLogic.sol | 39 ------------------ contracts/airswap/IFeeLogic.sol | 11 ----- 3 files changed, 35 insertions(+), 65 deletions(-) delete mode 100644 contracts/airswap/FeeLogic.sol delete mode 100644 contracts/airswap/IFeeLogic.sol diff --git a/contracts/airswap/AirSwapFeeConnector.sol b/contracts/airswap/AirSwapFeeConnector.sol index b1870588a..cbcff81e5 100644 --- a/contracts/airswap/AirSwapFeeConnector.sol +++ b/contracts/airswap/AirSwapFeeConnector.sol @@ -1,24 +1,28 @@ pragma solidity 0.5.17; import "../openzeppelin/SafeMath.sol"; -import "../openzeppelin/Ownable.sol"; +import "../openzeppelin/PausableOz.sol"; import "../openzeppelin/IERC20_.sol"; import "./IAirSwapFeeConnector.sol"; -import "./IFeeLogic.sol"; import "./IFeeVault.sol"; import "./ISwapERC20.sol"; -contract AirSwapFeeConnector is Ownable, IAirSwapFeeConnector { +contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { using SafeMath for uint256; - address public feeLogicAddress; - address public feeVaultAddress; - address public swapERC20Address; + uint256 public constant POINTS = 1000; + + uint256 public inputFeeInPoints = 0; + uint256 public outputFeeInPoints = 0; + + address public feeVaultAddress = address(0); + address public swapERC20Address = address(0); - event FeeLogicAddressChangedEvent(address indexed sender, address newAddress); event FeeVaultAddressChangedEvent(address indexed sender, address newAddress); event SwapERC20AddressChangedEvent(address indexed sender, address newAddress); + event InputFeeChanged(address indexed sender, uint256 feeInPoints); + event OutputFeeChanged(address indexed sender, uint256 feeInPoints); event SwapEvent( address indexed sender, @@ -31,23 +35,36 @@ contract AirSwapFeeConnector is Ownable, IAirSwapFeeConnector { uint256 outputFee ); - /*** setters ***/ + function setInputFee(uint256 _inputFeeInPoints) public onlyOwner { + inputFeeInPoints = _inputFeeInPoints; + emit InputFeeChanged(msg.sender, inputFeeInPoints); + } + + function setOutputFee(uint256 _outputFeeInPoints) public onlyOwner { + outputFeeInPoints = _outputFeeInPoints; + emit OutputFeeChanged(msg.sender, outputFeeInPoints); + } function setFeeVaultAddress(address _newAddress) public onlyOwner { feeVaultAddress = _newAddress; + require(feeVaultAddress != address(0), "invalid vault"); emit FeeVaultAddressChangedEvent(msg.sender, feeVaultAddress); } - function setFeeLogicAddress(address _newAddress) public onlyOwner { - feeLogicAddress = _newAddress; - emit FeeLogicAddressChangedEvent(msg.sender, feeLogicAddress); - } - function setSwapERC20Address(address _newAddress) public onlyOwner { swapERC20Address = _newAddress; + require(swapERC20Address != address(0), "invalid swapper"); emit SwapERC20AddressChangedEvent(msg.sender, swapERC20Address); } + function calculateInputFee(uint256 _sendAmount) public view returns (uint256) { + return _sendAmount.mul(inputFeeInPoints).div(POINTS); + } + + function calculateOutputFee(uint256 _receiveAmount) public view returns (uint256) { + return _receiveAmount.mul(outputFeeInPoints).div(POINTS); + } + function swap( address _senderToken, uint256 _totalSenderAmount, @@ -63,6 +80,9 @@ contract AirSwapFeeConnector is Ownable, IAirSwapFeeConnector { ) public { address sender = msg.sender; + require(feeVaultAddress != address(0), "invalid vault"); + require(swapERC20Address != address(0), "invalid swapper"); + // first we move all the funds here require( @@ -71,7 +91,7 @@ contract AirSwapFeeConnector is Ownable, IAirSwapFeeConnector { ); // then we collect the input fee - uint256 inputFee = IFeeLogic(feeLogicAddress).calculateInputFee(_totalSenderAmount); + uint256 inputFee = calculateInputFee(_totalSenderAmount); require( IERC20_(_senderToken).transferFrom(address(this), feeVaultAddress, inputFee), "transfer failed" @@ -95,7 +115,7 @@ contract AirSwapFeeConnector is Ownable, IAirSwapFeeConnector { ); // now we collect the output fee - uint256 outputFee = IFeeLogic(feeLogicAddress).calculateOutputFee(_signerAmount); + uint256 outputFee = calculateOutputFee(_signerAmount); require( IERC20_(_signerToken).transferFrom(address(this), feeVaultAddress, outputFee), "transfer failed" diff --git a/contracts/airswap/FeeLogic.sol b/contracts/airswap/FeeLogic.sol deleted file mode 100644 index c5e431a32..000000000 --- a/contracts/airswap/FeeLogic.sol +++ /dev/null @@ -1,39 +0,0 @@ -pragma solidity 0.5.17; - -import "../openzeppelin/SafeMath.sol"; -import "../openzeppelin/Ownable.sol"; -import "../openzeppelin/IERC20_.sol"; - -import "./IFeeLogic.sol"; - -contract FeeLogic is Ownable, IFeeLogic { - using SafeMath for uint256; - - uint256 public constant POINTS = 1000; - - uint256 public inputFeeInPoints; - uint256 public outputFeeInPoints; - - event InputFeeChanged(address indexed sender, uint256 feeInPoints); - event OutputFeeChanged(address indexed sender, uint256 feeInPoints); - - /*** setters ***/ - - function setInputFee(uint256 _inputFeeInPoints) public onlyOwner { - inputFeeInPoints = _inputFeeInPoints; - emit InputFeeChanged(msg.sender, inputFeeInPoints); - } - - function setOutputFee(uint256 _outputFeeInPoints) public onlyOwner { - outputFeeInPoints = _outputFeeInPoints; - emit OutputFeeChanged(msg.sender, outputFeeInPoints); - } - - function calculateInputFee(uint256 _sendAmount) public returns (uint256) { - return _sendAmount.mul(inputFeeInPoints).div(POINTS); - } - - function calculateOutputFee(uint256 _receiveAmount) public returns (uint256) { - return _receiveAmount.mul(outputFeeInPoints).div(POINTS); - } -} diff --git a/contracts/airswap/IFeeLogic.sol b/contracts/airswap/IFeeLogic.sol deleted file mode 100644 index 646607139..000000000 --- a/contracts/airswap/IFeeLogic.sol +++ /dev/null @@ -1,11 +0,0 @@ -pragma solidity 0.5.17; - -interface IFeeLogic { - function setInputFee(uint256 _inputFeeInPoints) external; - - function setOutputFee(uint256 _outputFeeInPoints) external; - - function calculateInputFee(uint256 sendAmount) external returns (uint256); - - function calculateOutputFee(uint256 receiveAmount) external returns (uint256); -} From ff6f865b46e7082971d6041dad6eb095a974b5e3 Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Sun, 22 Oct 2023 15:23:44 +0300 Subject: [PATCH 04/12] added inline doc --- contracts/airswap/AirSwapFeeConnector.sol | 26 ++++++++++++++-- contracts/airswap/FeeVault.sol | 16 ---------- contracts/airswap/IAirSwapFeeConnector.sol | 36 ++++++++++++++++++++-- contracts/airswap/IFeeVault.sol | 9 ------ 4 files changed, 58 insertions(+), 29 deletions(-) delete mode 100644 contracts/airswap/FeeVault.sol delete mode 100644 contracts/airswap/IFeeVault.sol diff --git a/contracts/airswap/AirSwapFeeConnector.sol b/contracts/airswap/AirSwapFeeConnector.sol index cbcff81e5..3c42c8f1f 100644 --- a/contracts/airswap/AirSwapFeeConnector.sol +++ b/contracts/airswap/AirSwapFeeConnector.sol @@ -5,7 +5,6 @@ import "../openzeppelin/PausableOz.sol"; import "../openzeppelin/IERC20_.sol"; import "./IAirSwapFeeConnector.sol"; -import "./IFeeVault.sol"; import "./ISwapERC20.sol"; contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { @@ -35,22 +34,33 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { uint256 outputFee ); + /// @notice Set the input fee in points, ie 25 means 2.5 percent. + /// The input fee is collected on the sent tokens before + /// the actual conversion. + /// @param inputFeeInPoints The new fee in points function setInputFee(uint256 _inputFeeInPoints) public onlyOwner { inputFeeInPoints = _inputFeeInPoints; emit InputFeeChanged(msg.sender, inputFeeInPoints); } + /// @notice Set the output fee in points, ie 25 means 2.5 percent. + /// The output fee is collected after the conversion. + /// @param outputFeeInPoints The new fee in points function setOutputFee(uint256 _outputFeeInPoints) public onlyOwner { outputFeeInPoints = _outputFeeInPoints; emit OutputFeeChanged(msg.sender, outputFeeInPoints); } + /// @notice Set the address to which fees are sent + /// @param _newAddress The new address function setFeeVaultAddress(address _newAddress) public onlyOwner { feeVaultAddress = _newAddress; require(feeVaultAddress != address(0), "invalid vault"); emit FeeVaultAddressChangedEvent(msg.sender, feeVaultAddress); } + /// @notice Set the address of the AirSwap contract + /// @param _newAddress The new address function setSwapERC20Address(address _newAddress) public onlyOwner { swapERC20Address = _newAddress; require(swapERC20Address != address(0), "invalid swapper"); @@ -65,6 +75,18 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { return _receiveAmount.mul(outputFeeInPoints).div(POINTS); } + /// @notice Swap one token for another. + /// @param _senderToken The token which is to be sent + /// @param _totalSenderAmount The amount to be sent before the input fee is collected. + /// @param _signerWallet Address of the market maker wallet + /// @param _signerToken Address of the token to convert to + /// @param _signerAmount Amount of resulting token from the conversion + /// @param _recipient Address to send the resulting tokens after collecting the output fee + /// @param _nonce A one time nonce + /// @param _expiry Date at which the original proposal will expire + /// @param _v v part of the ECDSA signature + /// @param _r r part of the ECDSA signature + /// @param _s s part of the ECDSA signature function swap( address _senderToken, uint256 _totalSenderAmount, @@ -84,7 +106,6 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { require(swapERC20Address != address(0), "invalid swapper"); // first we move all the funds here - require( IERC20_(_senderToken).transferFrom(sender, address(this), _totalSenderAmount), "transfer failed" @@ -129,6 +150,7 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { "transfer failed" ); + // emit the event emit SwapEvent( sender, _recipient, diff --git a/contracts/airswap/FeeVault.sol b/contracts/airswap/FeeVault.sol deleted file mode 100644 index 3e04a182a..000000000 --- a/contracts/airswap/FeeVault.sol +++ /dev/null @@ -1,16 +0,0 @@ -pragma solidity 0.5.17; - -import "../openzeppelin/Ownable.sol"; -import "../openzeppelin/IERC20_.sol"; - -import "./IFeeVault.sol"; - -contract FeeVault is Ownable, IFeeVault { - function sendFunds( - address _token, - address _recipient, - uint256 _amount - ) public onlyOwner returns (bool) { - return IERC20_(_token).transfer(_recipient, _amount); - } -} diff --git a/contracts/airswap/IAirSwapFeeConnector.sol b/contracts/airswap/IAirSwapFeeConnector.sol index 2ca58b7ea..7051ded53 100644 --- a/contracts/airswap/IAirSwapFeeConnector.sol +++ b/contracts/airswap/IAirSwapFeeConnector.sol @@ -1,10 +1,42 @@ pragma solidity 0.5.17; +/** + * @title A proxy to the AirSwap ERC20 contract that collects fees before and after the conversion. + * @author Derek Mattr dharkmattr@gmail.com + */ interface IAirSwapFeeConnector { - function setFeeLogic(address feeLogicAddress) external; - function setFeeVault(address feeVaultAddress) external; + /// @notice Set the input fee in points, ie 25 means 2.5 percent. + /// The input fee is collected on the sent tokens before + /// the actual conversion. + /// @param inputFeeInPoints The new fee in points + function setInputFee(uint256 inputFeeInPoints) external; + /// @notice Set the out fee in points, ie 25 means 2.5 percent. + /// The output fee is collecte after the conversion. + /// @param outputFeeInPoints The new fee in points + function setOutputFee(uint256 outputFeeInPoints) external; + + /// @notice Set the address to which fees are sent + /// @param newAddress The new address + function setFeeVaultAddress(address newAddress) external; + + /// @notice Set the address of the AirSwap contract + /// @param newAddress The new address + function setSwapERC20Address(address newAddress) external; + + /// @notice Swap one token for another. + /// @param senderToken The token which is to be sent + /// @param totalSenderAmount The amount to be sent before the input fee is collected. + /// @param signerWallet Address of the market maker wallet + /// @param signerAmount Amount of resulting token from the conversion + /// @param recipient Address to send the resulting tokens after collecting the output fee + /// @param recipient Address to send the resulting tokens after collecting the output fee + /// @param nonce A one time nonce + /// @param expiry Date at which the original proposal will expire + /// @param v v part of the ECDSA signature + /// @param r r part of the ECDSA signature + /// @param s s part of the ECDSA signature function swap( address senderToken, uint256 totalSenderAmount, diff --git a/contracts/airswap/IFeeVault.sol b/contracts/airswap/IFeeVault.sol deleted file mode 100644 index ed1282fee..000000000 --- a/contracts/airswap/IFeeVault.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity 0.5.17; - -interface IFeeVault { - function sendFunds( - address token, - address recipient, - uint256 amount - ) external returns (bool); -} From 2883a8ff90de3f0b5dd34370010c4fd2aefceb34 Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Wed, 1 Nov 2023 12:16:27 +0200 Subject: [PATCH 05/12] better rounding --- contracts/airswap/AirSwapFeeConnector.sol | 121 +++++++----- contracts/airswap/IAirSwapFeeConnector.sol | 48 ++--- contracts/mockup/AirSwapERC20Mockup.sol | 68 +++++++ hardhat.config.js | 2 +- tests/airswap/airswap-integration.js | 202 +++++++++++++++++++++ 5 files changed, 370 insertions(+), 71 deletions(-) create mode 100644 contracts/mockup/AirSwapERC20Mockup.sol create mode 100644 tests/airswap/airswap-integration.js diff --git a/contracts/airswap/AirSwapFeeConnector.sol b/contracts/airswap/AirSwapFeeConnector.sol index 3c42c8f1f..a3dc32b11 100644 --- a/contracts/airswap/AirSwapFeeConnector.sol +++ b/contracts/airswap/AirSwapFeeConnector.sol @@ -10,6 +10,21 @@ import "./ISwapERC20.sol"; contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { using SafeMath for uint256; + struct SwapRequest { + address sender; + address recipient; + uint256 nonce; + uint256 expiry; + address signerWallet; + address signerToken; + uint256 signerAmount; + address senderToken; + uint256 totalSenderAmount; + uint8 v; + bytes32 r; + bytes32 s; + } + uint256 public constant POINTS = 1000; uint256 public inputFeeInPoints = 0; @@ -20,8 +35,8 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { event FeeVaultAddressChangedEvent(address indexed sender, address newAddress); event SwapERC20AddressChangedEvent(address indexed sender, address newAddress); - event InputFeeChanged(address indexed sender, uint256 feeInPoints); - event OutputFeeChanged(address indexed sender, uint256 feeInPoints); + event InputFeeChangedEvent(address indexed sender, uint256 feeInPoints); + event OutputFeeChangedEvent(address indexed sender, uint256 feeInPoints); event SwapEvent( address indexed sender, @@ -35,20 +50,20 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { ); /// @notice Set the input fee in points, ie 25 means 2.5 percent. - /// The input fee is collected on the sent tokens before + /// The input fee is collected on the sent tokens before /// the actual conversion. - /// @param inputFeeInPoints The new fee in points + /// @param _inputFeeInPoints The new fee in points function setInputFee(uint256 _inputFeeInPoints) public onlyOwner { inputFeeInPoints = _inputFeeInPoints; - emit InputFeeChanged(msg.sender, inputFeeInPoints); + emit InputFeeChangedEvent(msg.sender, inputFeeInPoints); } /// @notice Set the output fee in points, ie 25 means 2.5 percent. /// The output fee is collected after the conversion. - /// @param outputFeeInPoints The new fee in points + /// @param _outputFeeInPoints The new fee in points function setOutputFee(uint256 _outputFeeInPoints) public onlyOwner { outputFeeInPoints = _outputFeeInPoints; - emit OutputFeeChanged(msg.sender, outputFeeInPoints); + emit OutputFeeChangedEvent(msg.sender, outputFeeInPoints); } /// @notice Set the address to which fees are sent @@ -76,89 +91,103 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { } /// @notice Swap one token for another. - /// @param _senderToken The token which is to be sent - /// @param _totalSenderAmount The amount to be sent before the input fee is collected. - /// @param _signerWallet Address of the market maker wallet - /// @param _signerToken Address of the token to convert to - /// @param _signerAmount Amount of resulting token from the conversion + /// @param _sender Address which is sending the tokens /// @param _recipient Address to send the resulting tokens after collecting the output fee /// @param _nonce A one time nonce /// @param _expiry Date at which the original proposal will expire + /// @param _signerWallet Address of the market maker wallet + /// @param _signerToken Address of the token to convert to + /// @param _signerAmount Amount of resulting token from the conversion + /// @param _totalSenderAmount The amount to be sent before the input fee is collected. /// @param _v v part of the ECDSA signature /// @param _r r part of the ECDSA signature /// @param _s s part of the ECDSA signature function swap( - address _senderToken, - uint256 _totalSenderAmount, - address _signerWallet, - address _signerToken, - uint256 _signerAmount, + address _sender, address _recipient, uint256 _nonce, uint256 _expiry, + address _signerWallet, + address _signerToken, + uint256 _signerAmount, + address _senderToken, + uint256 _totalSenderAmount, uint8 _v, bytes32 _r, bytes32 _s ) public { - address sender = msg.sender; - require(feeVaultAddress != address(0), "invalid vault"); require(swapERC20Address != address(0), "invalid swapper"); + SwapRequest memory swapRequest = SwapRequest( + _sender, + _recipient, + _nonce, + _expiry, + _signerWallet, + _signerToken, + _signerAmount, + _senderToken, + _totalSenderAmount, + _v, + _r, + _s + ); + // first we move all the funds here require( - IERC20_(_senderToken).transferFrom(sender, address(this), _totalSenderAmount), - "transfer failed" + IERC20_(swapRequest.senderToken).transferFrom(swapRequest.sender, address(this), swapRequest.totalSenderAmount), + "transfer failed 1" ); // then we collect the input fee - uint256 inputFee = calculateInputFee(_totalSenderAmount); + uint256 inputFee = calculateInputFee(swapRequest.totalSenderAmount); require( - IERC20_(_senderToken).transferFrom(address(this), feeVaultAddress, inputFee), - "transfer failed" + IERC20_(swapRequest.senderToken).transfer(feeVaultAddress, inputFee), + "transfer failed 2" ); - uint256 senderAmountAfterFee = _totalSenderAmount.sub(inputFee); + uint256 senderAmountAfterFee = swapRequest.totalSenderAmount.sub(inputFee); // now we do the swap ISwapERC20(swapERC20Address).swap( address(this), - _nonce, - _expiry, - _signerWallet, - _signerToken, - _signerAmount, - _senderToken, + swapRequest.nonce, + swapRequest.expiry, + swapRequest.signerWallet, + swapRequest.signerToken, + swapRequest.signerAmount, + swapRequest.senderToken, senderAmountAfterFee, - _v, - _r, - _s + swapRequest.v, + swapRequest.r, + swapRequest.s ); // now we collect the output fee - uint256 outputFee = calculateOutputFee(_signerAmount); + uint256 outputFee = calculateOutputFee(swapRequest.signerAmount); require( - IERC20_(_signerToken).transferFrom(address(this), feeVaultAddress, outputFee), - "transfer failed" + IERC20_(swapRequest.signerToken).transfer(feeVaultAddress, outputFee), + "transfer failed 3" ); - uint256 receiveAmountAfterFee = _signerAmount.sub(outputFee); + uint256 receiveAmountAfterFee = swapRequest.signerAmount.sub(outputFee); // now we send the user her due require( - IERC20_(_signerToken).transferFrom(address(this), _recipient, receiveAmountAfterFee), - "transfer failed" + IERC20_(swapRequest.signerToken).transfer(swapRequest.recipient, receiveAmountAfterFee), + "transfer failed 4" ); // emit the event emit SwapEvent( - sender, - _recipient, - _senderToken, - _totalSenderAmount, + swapRequest.sender, + swapRequest.recipient, + swapRequest.senderToken, + swapRequest.totalSenderAmount, inputFee, - _signerToken, - _signerAmount, + swapRequest.signerToken, + receiveAmountAfterFee, outputFee ); } diff --git a/contracts/airswap/IAirSwapFeeConnector.sol b/contracts/airswap/IAirSwapFeeConnector.sol index 7051ded53..172c5ad27 100644 --- a/contracts/airswap/IAirSwapFeeConnector.sol +++ b/contracts/airswap/IAirSwapFeeConnector.sol @@ -5,9 +5,8 @@ pragma solidity 0.5.17; * @author Derek Mattr dharkmattr@gmail.com */ interface IAirSwapFeeConnector { - /// @notice Set the input fee in points, ie 25 means 2.5 percent. - /// The input fee is collected on the sent tokens before + /// The input fee is collected on the sent tokens before /// the actual conversion. /// @param inputFeeInPoints The new fee in points function setInputFee(uint256 inputFeeInPoints) external; @@ -26,28 +25,29 @@ interface IAirSwapFeeConnector { function setSwapERC20Address(address newAddress) external; /// @notice Swap one token for another. - /// @param senderToken The token which is to be sent - /// @param totalSenderAmount The amount to be sent before the input fee is collected. - /// @param signerWallet Address of the market maker wallet - /// @param signerAmount Amount of resulting token from the conversion - /// @param recipient Address to send the resulting tokens after collecting the output fee - /// @param recipient Address to send the resulting tokens after collecting the output fee - /// @param nonce A one time nonce - /// @param expiry Date at which the original proposal will expire - /// @param v v part of the ECDSA signature - /// @param r r part of the ECDSA signature - /// @param s s part of the ECDSA signature + /// @param _sender Address which is sending the tokens + /// @param _recipient Address to send the resulting tokens after collecting the output fee + /// @param _nonce A one time nonce + /// @param _expiry Date at which the original proposal will expire + /// @param _signerWallet Address of the market maker wallet + /// @param _signerToken Address of the token to convert to + /// @param _signerAmount Amount of resulting token from the conversion + /// @param _totalSenderAmount The amount to be sent before the input fee is collected. + /// @param _v v part of the ECDSA signature + /// @param _r r part of the ECDSA signature + /// @param _s s part of the ECDSA signature function swap( - address senderToken, - uint256 totalSenderAmount, - address signerWallet, - address signerToken, - uint256 signerAmount, - address recipient, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s + address _sender, + address _recipient, + uint256 _nonce, + uint256 _expiry, + address _signerWallet, + address _signerToken, + uint256 _signerAmount, + address _senderToken, + uint256 _totalSenderAmount, + uint8 _v, + bytes32 _r, + bytes32 _s ) external; } diff --git a/contracts/mockup/AirSwapERC20Mockup.sol b/contracts/mockup/AirSwapERC20Mockup.sol new file mode 100644 index 000000000..d8acf7450 --- /dev/null +++ b/contracts/mockup/AirSwapERC20Mockup.sol @@ -0,0 +1,68 @@ +pragma solidity 0.5.17; + +import "../openzeppelin/IERC20_.sol"; +import "../airswap/ISwapERC20.sol"; + +// This contract is only for testing purposes +contract AirSwapERC20Mockup is ISwapERC20 { + + int16 public swapCalled = 0; + + address public recipient; + uint256 public nonce; + uint256 public expiry; + address public signerWallet; + address public signerToken; + uint256 public signerAmount; + address public senderToken; + uint256 public senderAmount; + uint8 public v; + bytes32 public r; + bytes32 public s; + + function reset() public { + recipient = address(0); + nonce = 0; + expiry = 0; + signerWallet = address(0); + signerToken = address(0); + signerAmount = 0; + senderToken = address(0); + senderAmount = 0; + v = 0; + r = 0; + s = 0; + + swapCalled = 0; + } + + function swap( + address _recipient, + uint256 _nonce, + uint256 _expiry, + address _signerWallet, + address _signerToken, + uint256 _signerAmount, + address _senderToken, + uint256 _senderAmount, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external { + recipient = _recipient; + nonce = _nonce; + expiry = _expiry; + signerWallet = _signerWallet; + signerToken = _signerToken; + signerAmount = _signerAmount; + senderToken = _senderToken; + senderAmount = _senderAmount; + v = _v; + r = _r; + s = _s; + + swapCalled++; + + IERC20_(_signerToken).transfer(_recipient, _signerAmount); + } +} diff --git a/hardhat.config.js b/hardhat.config.js index b920f012d..3353a6640 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -12,7 +12,7 @@ require("hardhat-log-remover"); require("hardhat-abi-exporter"); require("hardhat-deploy"); require("@nomicfoundation/hardhat-chai-matchers"); -require("@nomicfoundation/hardhat-foundry"); +//require("@nomicfoundation/hardhat-foundry"); require("./hardhat/tasks"); diff --git a/tests/airswap/airswap-integration.js b/tests/airswap/airswap-integration.js new file mode 100644 index 000000000..3a0fd89ed --- /dev/null +++ b/tests/airswap/airswap-integration.js @@ -0,0 +1,202 @@ +const { expectRevert, expectEvent, BN, constants } = require("@openzeppelin/test-helpers"); +const { artifacts } = require("hardhat"); + +const AirSwapERC20Mockup = artifacts.require("AirSwapERC20Mockup"); +const AirSwapFeeConnector = artifacts.require("AirSwapFeeConnector"); +const TestToken = artifacts.require("TestToken"); + +const { ZERO_ADDRESS } = constants; +const ONE = new BN('1000000000000000000'); +const INITIAL_MINTED = 1000000; +const INITIAL_AMOUNT_BN = ONE.mul(new BN(INITIAL_MINTED)); +const wei = web3.utils.toWei; +const hunEth = new BN(wei("100", "ether")); + +function bnToComparableNumber(bn) { + // round bn to 9 digits + return bn.divRound(new BN('1000000000')).toNumber() / 10**9; +} + +contract("AirSwap Integration", (accounts) => { + + let deployerAddress, senderAddress, marketMakerAddress, recipientAddress, feeVaultAddress; + let testToken1, testToken1Address, testToken2, testToken2Address; + let airSwapERC20Mockup, airSwapFeeConnector; + let fakeAddress; + + describe.only("AirSwap Integration test", async () => { + + before(async () => { + [ deployerAddress, senderAddress, marketMakerAddress, recipientAddress, feeVaultAddress ] = accounts; + + airSwapERC20Mockup = await AirSwapERC20Mockup.new(); + airSwapFeeConnector = await AirSwapFeeConnector.new(); + + testToken1 = await TestToken.new("TST1", "TST1", 18, INITIAL_AMOUNT_BN); + testToken1Address = testToken1.address; + testToken2 = await TestToken.new("TST2", "TST2", 18, INITIAL_AMOUNT_BN); + testToken2Address = testToken2.address; + + await testToken2.transfer(airSwapERC20Mockup.address, INITIAL_AMOUNT_BN); + }); + + beforeEach(async () => { + await airSwapERC20Mockup.reset(); + fakeAddress = (await TestToken.new('', '', 18, 0)).address; // just a random address + }); + + describe("inputFee", async () => { + const fakeFee = new BN(Math.floor(10 ^ 18 * Math.random)); + it("owner only", async () => { + await expectRevert( + airSwapFeeConnector.setInputFee(fakeFee, { from: senderAddress }), + "unauthorized" + ); + }); + it("can be set", async () => { + const { tx } = await airSwapFeeConnector.setInputFee(fakeFee); + const fee = await airSwapFeeConnector.inputFeeInPoints(); + expect(fee.toString()).to.be.equal(fakeFee.toString()); + await expectEvent.inTransaction(tx, airSwapFeeConnector, "InputFeeChangedEvent", { + sender: deployerAddress, + feeInPoints: fakeFee + }); + }); + }); + + describe("outputFee", async () => { + const fakeFee = new BN(Math.floor(10 ^ 18 * Math.random)); + it("owner only", async () => { + await expectRevert( + airSwapFeeConnector.setOutputFee(fakeFee, { from: senderAddress }), + "unauthorized" + ); + }); + it("can be set", async () => { + const { tx } = await airSwapFeeConnector.setOutputFee(fakeFee); + const fee = await airSwapFeeConnector.outputFeeInPoints(); + expect(fee.toString()).to.be.equal(fakeFee.toString()); + await expectEvent.inTransaction(tx, airSwapFeeConnector, "OutputFeeChangedEvent", { + sender: deployerAddress, + feeInPoints: fakeFee + }); + }); + }); + + describe("feeVaultAddress", async () => { + it("owner only", async () => { + await expectRevert( + airSwapFeeConnector.setFeeVaultAddress(fakeAddress, { from: senderAddress }), + "unauthorized" + ); + }); + it("can be set", async () => { + const { tx } = await airSwapFeeConnector.setFeeVaultAddress(fakeAddress); + const address = await airSwapFeeConnector.feeVaultAddress(); + expect(address).to.be.equal(fakeAddress); + await expectEvent.inTransaction(tx, airSwapFeeConnector, "FeeVaultAddressChangedEvent", { + sender: deployerAddress, + newAddress: fakeAddress + }); + }); + }); + + describe("swapERC20Address", async () => { + it("owner only", async () => { + await expectRevert( + airSwapFeeConnector.setSwapERC20Address(fakeAddress, { from: senderAddress }), + "unauthorized" + ); + }); + it("can be set", async () => { + const { tx } = await airSwapFeeConnector.setSwapERC20Address(fakeAddress); + const address = await airSwapFeeConnector.swapERC20Address(); + expect(address).to.be.equal(fakeAddress); + await expectEvent.inTransaction(tx, airSwapFeeConnector, "SwapERC20AddressChangedEvent", { + sender: deployerAddress, + newAddress: fakeAddress + }); + }); + }); + + describe("happy flow", async () => { + it("swap is successful", async () => { + + const fakeNonce = 1; + const fakeExpiry = 1000000000; + const fakeV = 11; + const fakeR = '0x0101010101010101010101010101010101010101010101010101010101010101'; + const fakeS = '0x0202020202020202020202020202020202020202020202020202020202020202'; + + const POINTS = 1000; + const inputFeePoints = 310; // 31% + const outputFeePoints = 281; // 28.1% + const totalInputAmount = 1473; + const outputAmount = 871340; + const expectedInputFee = totalInputAmount * inputFeePoints / POINTS; + const inputAmountAfterFee = totalInputAmount - expectedInputFee; + const expectedOutputFee = outputAmount * outputFeePoints / POINTS; + const expectedOutputAmountAfterFee = outputAmount - expectedOutputFee; + + await airSwapFeeConnector.setSwapERC20Address(airSwapERC20Mockup.address); + await airSwapFeeConnector.setFeeVaultAddress(feeVaultAddress); + await airSwapFeeConnector.setInputFee(inputFeePoints); + await airSwapFeeConnector.setOutputFee(outputFeePoints); + await airSwapERC20Mockup.reset(); + + function numberToBn(n) { + return new BN(wei(n.toString(), "ether")); + } + + // first we need to approve + await testToken1.approve(airSwapFeeConnector.address, numberToBn(totalInputAmount)); + + // then we can convert + const { tx } = await airSwapFeeConnector.swap( + deployerAddress, + recipientAddress, + fakeNonce, + fakeExpiry, + marketMakerAddress, + testToken2Address, + numberToBn(outputAmount), + testToken1Address, + numberToBn(totalInputAmount), + fakeV, + fakeR, + fakeS + ); + + await expectEvent.inTransaction(tx, airSwapFeeConnector, "SwapEvent", { + sender: deployerAddress, + recipient: recipientAddress, + sendToken: testToken1Address, + sendAmount: numberToBn(totalInputAmount), + inputFee: numberToBn(expectedInputFee), + receiveToken: testToken2Address, + receiveAmount: numberToBn(expectedOutputAmountAfterFee), + outputFee: numberToBn(expectedOutputFee) + }); + + const actualRecipientBalance = await testToken2.balanceOf(recipientAddress); + expect(bnToComparableNumber(actualRecipientBalance)).to.equal(expectedOutputAmountAfterFee); + + const actualInputFeeCollected = await testToken1.balanceOf(feeVaultAddress); + expect(bnToComparableNumber(actualInputFeeCollected)).to.equal(expectedInputFee); + + const actualOutputFeeCollected = await testToken2.balanceOf(feeVaultAddress); + expect(bnToComparableNumber(actualOutputFeeCollected)).to.equal(expectedOutputFee); + + const actualSenderBalance = await testToken1.balanceOf(deployerAddress); + expect(bnToComparableNumber(actualSenderBalance)).to.equal(INITIAL_MINTED - totalInputAmount); + + expect((await airSwapERC20Mockup.swapCalled()).toNumber()).is.equal(1); + expect((await airSwapERC20Mockup.v()).toNumber()).is.equal(fakeV); + expect((await airSwapERC20Mockup.r()).toString()).is.equal(fakeR); + expect((await airSwapERC20Mockup.s()).toString()).is.equal(fakeS); + expect((await airSwapERC20Mockup.nonce()).toNumber()).is.equal(fakeNonce); + expect((await airSwapERC20Mockup.expiry()).toNumber()).is.equal(fakeExpiry); + }); + }); + }); +}); From 7e2d129fc2f3731fd48bb7d04c925e633da843cc Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Wed, 1 Nov 2023 12:18:43 +0200 Subject: [PATCH 06/12] re-emable foundry --- contracts/airswap/AirSwapFeeConnector.sol | 64 +++++++++-------- contracts/mockup/AirSwapERC20Mockup.sol | 1 - hardhat.config.js | 2 +- tests/airswap/airswap-integration.js | 88 ++++++++++++++--------- 4 files changed, 91 insertions(+), 64 deletions(-) diff --git a/contracts/airswap/AirSwapFeeConnector.sol b/contracts/airswap/AirSwapFeeConnector.sol index a3dc32b11..14b237cc8 100644 --- a/contracts/airswap/AirSwapFeeConnector.sol +++ b/contracts/airswap/AirSwapFeeConnector.sol @@ -11,18 +11,18 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { using SafeMath for uint256; struct SwapRequest { - address sender; - address recipient; - uint256 nonce; - uint256 expiry; - address signerWallet; - address signerToken; - uint256 signerAmount; - address senderToken; - uint256 totalSenderAmount; - uint8 v; - bytes32 r; - bytes32 s; + address sender; + address recipient; + uint256 nonce; + uint256 expiry; + address signerWallet; + address signerToken; + uint256 signerAmount; + address senderToken; + uint256 totalSenderAmount; + uint8 v; + bytes32 r; + bytes32 s; } uint256 public constant POINTS = 1000; @@ -119,24 +119,29 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { require(feeVaultAddress != address(0), "invalid vault"); require(swapERC20Address != address(0), "invalid swapper"); - SwapRequest memory swapRequest = SwapRequest( - _sender, - _recipient, - _nonce, - _expiry, - _signerWallet, - _signerToken, - _signerAmount, - _senderToken, - _totalSenderAmount, - _v, - _r, - _s - ); + SwapRequest memory swapRequest = + SwapRequest( + _sender, + _recipient, + _nonce, + _expiry, + _signerWallet, + _signerToken, + _signerAmount, + _senderToken, + _totalSenderAmount, + _v, + _r, + _s + ); // first we move all the funds here require( - IERC20_(swapRequest.senderToken).transferFrom(swapRequest.sender, address(this), swapRequest.totalSenderAmount), + IERC20_(swapRequest.senderToken).transferFrom( + swapRequest.sender, + address(this), + swapRequest.totalSenderAmount + ), "transfer failed 1" ); @@ -175,7 +180,10 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { // now we send the user her due require( - IERC20_(swapRequest.signerToken).transfer(swapRequest.recipient, receiveAmountAfterFee), + IERC20_(swapRequest.signerToken).transfer( + swapRequest.recipient, + receiveAmountAfterFee + ), "transfer failed 4" ); diff --git a/contracts/mockup/AirSwapERC20Mockup.sol b/contracts/mockup/AirSwapERC20Mockup.sol index d8acf7450..9db32f99c 100644 --- a/contracts/mockup/AirSwapERC20Mockup.sol +++ b/contracts/mockup/AirSwapERC20Mockup.sol @@ -5,7 +5,6 @@ import "../airswap/ISwapERC20.sol"; // This contract is only for testing purposes contract AirSwapERC20Mockup is ISwapERC20 { - int16 public swapCalled = 0; address public recipient; diff --git a/hardhat.config.js b/hardhat.config.js index 3353a6640..b920f012d 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -12,7 +12,7 @@ require("hardhat-log-remover"); require("hardhat-abi-exporter"); require("hardhat-deploy"); require("@nomicfoundation/hardhat-chai-matchers"); -//require("@nomicfoundation/hardhat-foundry"); +require("@nomicfoundation/hardhat-foundry"); require("./hardhat/tasks"); diff --git a/tests/airswap/airswap-integration.js b/tests/airswap/airswap-integration.js index 3a0fd89ed..19dd4e72c 100644 --- a/tests/airswap/airswap-integration.js +++ b/tests/airswap/airswap-integration.js @@ -6,7 +6,7 @@ const AirSwapFeeConnector = artifacts.require("AirSwapFeeConnector"); const TestToken = artifacts.require("TestToken"); const { ZERO_ADDRESS } = constants; -const ONE = new BN('1000000000000000000'); +const ONE = new BN("1000000000000000000"); const INITIAL_MINTED = 1000000; const INITIAL_AMOUNT_BN = ONE.mul(new BN(INITIAL_MINTED)); const wei = web3.utils.toWei; @@ -14,24 +14,28 @@ const hunEth = new BN(wei("100", "ether")); function bnToComparableNumber(bn) { // round bn to 9 digits - return bn.divRound(new BN('1000000000')).toNumber() / 10**9; + return bn.divRound(new BN("1000000000")).toNumber() / 10 ** 9; } contract("AirSwap Integration", (accounts) => { - let deployerAddress, senderAddress, marketMakerAddress, recipientAddress, feeVaultAddress; let testToken1, testToken1Address, testToken2, testToken2Address; let airSwapERC20Mockup, airSwapFeeConnector; let fakeAddress; describe.only("AirSwap Integration test", async () => { - before(async () => { - [ deployerAddress, senderAddress, marketMakerAddress, recipientAddress, feeVaultAddress ] = accounts; - + [ + deployerAddress, + senderAddress, + marketMakerAddress, + recipientAddress, + feeVaultAddress, + ] = accounts; + airSwapERC20Mockup = await AirSwapERC20Mockup.new(); airSwapFeeConnector = await AirSwapFeeConnector.new(); - + testToken1 = await TestToken.new("TST1", "TST1", 18, INITIAL_AMOUNT_BN); testToken1Address = testToken1.address; testToken2 = await TestToken.new("TST2", "TST2", 18, INITIAL_AMOUNT_BN); @@ -39,14 +43,14 @@ contract("AirSwap Integration", (accounts) => { await testToken2.transfer(airSwapERC20Mockup.address, INITIAL_AMOUNT_BN); }); - + beforeEach(async () => { await airSwapERC20Mockup.reset(); - fakeAddress = (await TestToken.new('', '', 18, 0)).address; // just a random address + fakeAddress = (await TestToken.new("", "", 18, 0)).address; // just a random address }); - + describe("inputFee", async () => { - const fakeFee = new BN(Math.floor(10 ^ 18 * Math.random)); + const fakeFee = new BN(Math.floor(10 ^ (18 * Math.random))); it("owner only", async () => { await expectRevert( airSwapFeeConnector.setInputFee(fakeFee, { from: senderAddress }), @@ -59,13 +63,13 @@ contract("AirSwap Integration", (accounts) => { expect(fee.toString()).to.be.equal(fakeFee.toString()); await expectEvent.inTransaction(tx, airSwapFeeConnector, "InputFeeChangedEvent", { sender: deployerAddress, - feeInPoints: fakeFee + feeInPoints: fakeFee, }); }); }); describe("outputFee", async () => { - const fakeFee = new BN(Math.floor(10 ^ 18 * Math.random)); + const fakeFee = new BN(Math.floor(10 ^ (18 * Math.random))); it("owner only", async () => { await expectRevert( airSwapFeeConnector.setOutputFee(fakeFee, { from: senderAddress }), @@ -78,7 +82,7 @@ contract("AirSwap Integration", (accounts) => { expect(fee.toString()).to.be.equal(fakeFee.toString()); await expectEvent.inTransaction(tx, airSwapFeeConnector, "OutputFeeChangedEvent", { sender: deployerAddress, - feeInPoints: fakeFee + feeInPoints: fakeFee, }); }); }); @@ -94,10 +98,15 @@ contract("AirSwap Integration", (accounts) => { const { tx } = await airSwapFeeConnector.setFeeVaultAddress(fakeAddress); const address = await airSwapFeeConnector.feeVaultAddress(); expect(address).to.be.equal(fakeAddress); - await expectEvent.inTransaction(tx, airSwapFeeConnector, "FeeVaultAddressChangedEvent", { - sender: deployerAddress, - newAddress: fakeAddress - }); + await expectEvent.inTransaction( + tx, + airSwapFeeConnector, + "FeeVaultAddressChangedEvent", + { + sender: deployerAddress, + newAddress: fakeAddress, + } + ); }); }); @@ -112,30 +121,34 @@ contract("AirSwap Integration", (accounts) => { const { tx } = await airSwapFeeConnector.setSwapERC20Address(fakeAddress); const address = await airSwapFeeConnector.swapERC20Address(); expect(address).to.be.equal(fakeAddress); - await expectEvent.inTransaction(tx, airSwapFeeConnector, "SwapERC20AddressChangedEvent", { - sender: deployerAddress, - newAddress: fakeAddress - }); + await expectEvent.inTransaction( + tx, + airSwapFeeConnector, + "SwapERC20AddressChangedEvent", + { + sender: deployerAddress, + newAddress: fakeAddress, + } + ); }); }); describe("happy flow", async () => { it("swap is successful", async () => { - const fakeNonce = 1; const fakeExpiry = 1000000000; const fakeV = 11; - const fakeR = '0x0101010101010101010101010101010101010101010101010101010101010101'; - const fakeS = '0x0202020202020202020202020202020202020202020202020202020202020202'; - + const fakeR = "0x0101010101010101010101010101010101010101010101010101010101010101"; + const fakeS = "0x0202020202020202020202020202020202020202020202020202020202020202"; + const POINTS = 1000; const inputFeePoints = 310; // 31% const outputFeePoints = 281; // 28.1% const totalInputAmount = 1473; const outputAmount = 871340; - const expectedInputFee = totalInputAmount * inputFeePoints / POINTS; + const expectedInputFee = (totalInputAmount * inputFeePoints) / POINTS; const inputAmountAfterFee = totalInputAmount - expectedInputFee; - const expectedOutputFee = outputAmount * outputFeePoints / POINTS; + const expectedOutputFee = (outputAmount * outputFeePoints) / POINTS; const expectedOutputAmountAfterFee = outputAmount - expectedOutputFee; await airSwapFeeConnector.setSwapERC20Address(airSwapERC20Mockup.address); @@ -148,9 +161,12 @@ contract("AirSwap Integration", (accounts) => { return new BN(wei(n.toString(), "ether")); } - // first we need to approve - await testToken1.approve(airSwapFeeConnector.address, numberToBn(totalInputAmount)); - + // first we need to approve + await testToken1.approve( + airSwapFeeConnector.address, + numberToBn(totalInputAmount) + ); + // then we can convert const { tx } = await airSwapFeeConnector.swap( deployerAddress, @@ -175,11 +191,13 @@ contract("AirSwap Integration", (accounts) => { inputFee: numberToBn(expectedInputFee), receiveToken: testToken2Address, receiveAmount: numberToBn(expectedOutputAmountAfterFee), - outputFee: numberToBn(expectedOutputFee) + outputFee: numberToBn(expectedOutputFee), }); const actualRecipientBalance = await testToken2.balanceOf(recipientAddress); - expect(bnToComparableNumber(actualRecipientBalance)).to.equal(expectedOutputAmountAfterFee); + expect(bnToComparableNumber(actualRecipientBalance)).to.equal( + expectedOutputAmountAfterFee + ); const actualInputFeeCollected = await testToken1.balanceOf(feeVaultAddress); expect(bnToComparableNumber(actualInputFeeCollected)).to.equal(expectedInputFee); @@ -188,7 +206,9 @@ contract("AirSwap Integration", (accounts) => { expect(bnToComparableNumber(actualOutputFeeCollected)).to.equal(expectedOutputFee); const actualSenderBalance = await testToken1.balanceOf(deployerAddress); - expect(bnToComparableNumber(actualSenderBalance)).to.equal(INITIAL_MINTED - totalInputAmount); + expect(bnToComparableNumber(actualSenderBalance)).to.equal( + INITIAL_MINTED - totalInputAmount + ); expect((await airSwapERC20Mockup.swapCalled()).toNumber()).is.equal(1); expect((await airSwapERC20Mockup.v()).toNumber()).is.equal(fakeV); From 0eea86adbf35bbf0f43b29234aeac7a8981a594f Mon Sep 17 00:00:00 2001 From: Tyrone Johnson Date: Fri, 1 Dec 2023 22:13:53 +0300 Subject: [PATCH 07/12] airswap integration chore: naming, folder struct --- .../airswap/AirswapFeeConnector.sol} | 14 ++--- .../airswap/IAirswapFeeConnector.sol} | 2 +- .../airswap/IAirswapSwapERC20.sol} | 2 +- contracts/mockup/AirSwapERC20Mockup.sol | 4 +- tests/airswap/airswap-integration.js | 56 +++++++++---------- 5 files changed, 39 insertions(+), 39 deletions(-) rename contracts/{airswap/AirSwapFeeConnector.sol => integrations/airswap/AirswapFeeConnector.sol} (95%) rename contracts/{airswap/IAirSwapFeeConnector.sol => integrations/airswap/IAirswapFeeConnector.sol} (98%) rename contracts/{airswap/ISwapERC20.sol => integrations/airswap/IAirswapSwapERC20.sol} (91%) diff --git a/contracts/airswap/AirSwapFeeConnector.sol b/contracts/integrations/airswap/AirswapFeeConnector.sol similarity index 95% rename from contracts/airswap/AirSwapFeeConnector.sol rename to contracts/integrations/airswap/AirswapFeeConnector.sol index 14b237cc8..ef111f14d 100644 --- a/contracts/airswap/AirSwapFeeConnector.sol +++ b/contracts/integrations/airswap/AirswapFeeConnector.sol @@ -1,13 +1,13 @@ pragma solidity 0.5.17; -import "../openzeppelin/SafeMath.sol"; -import "../openzeppelin/PausableOz.sol"; -import "../openzeppelin/IERC20_.sol"; +import "../../openzeppelin/SafeMath.sol"; +import "../../openzeppelin/PausableOz.sol"; +import "../../openzeppelin/IERC20_.sol"; -import "./IAirSwapFeeConnector.sol"; -import "./ISwapERC20.sol"; +import "./IAirswapFeeConnector.sol"; +import "./IAirswapSwapERC20.sol"; -contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { +contract AirswapFeeConnector is PausableOz, IAirswapFeeConnector { using SafeMath for uint256; struct SwapRequest { @@ -155,7 +155,7 @@ contract AirSwapFeeConnector is PausableOz, IAirSwapFeeConnector { uint256 senderAmountAfterFee = swapRequest.totalSenderAmount.sub(inputFee); // now we do the swap - ISwapERC20(swapERC20Address).swap( + IAirswapSwapERC20(swapERC20Address).swap( address(this), swapRequest.nonce, swapRequest.expiry, diff --git a/contracts/airswap/IAirSwapFeeConnector.sol b/contracts/integrations/airswap/IAirswapFeeConnector.sol similarity index 98% rename from contracts/airswap/IAirSwapFeeConnector.sol rename to contracts/integrations/airswap/IAirswapFeeConnector.sol index 172c5ad27..a4d60645b 100644 --- a/contracts/airswap/IAirSwapFeeConnector.sol +++ b/contracts/integrations/airswap/IAirswapFeeConnector.sol @@ -4,7 +4,7 @@ pragma solidity 0.5.17; * @title A proxy to the AirSwap ERC20 contract that collects fees before and after the conversion. * @author Derek Mattr dharkmattr@gmail.com */ -interface IAirSwapFeeConnector { +interface IAirswapFeeConnector { /// @notice Set the input fee in points, ie 25 means 2.5 percent. /// The input fee is collected on the sent tokens before /// the actual conversion. diff --git a/contracts/airswap/ISwapERC20.sol b/contracts/integrations/airswap/IAirswapSwapERC20.sol similarity index 91% rename from contracts/airswap/ISwapERC20.sol rename to contracts/integrations/airswap/IAirswapSwapERC20.sol index 3372f8127..57c62449d 100644 --- a/contracts/airswap/ISwapERC20.sol +++ b/contracts/integrations/airswap/IAirswapSwapERC20.sol @@ -1,6 +1,6 @@ pragma solidity 0.5.17; -interface ISwapERC20 { +interface IAirswapSwapERC20 { function swap( address recipient, uint256 nonce, diff --git a/contracts/mockup/AirSwapERC20Mockup.sol b/contracts/mockup/AirSwapERC20Mockup.sol index 9db32f99c..c7f7176b7 100644 --- a/contracts/mockup/AirSwapERC20Mockup.sol +++ b/contracts/mockup/AirSwapERC20Mockup.sol @@ -1,10 +1,10 @@ pragma solidity 0.5.17; import "../openzeppelin/IERC20_.sol"; -import "../airswap/ISwapERC20.sol"; +import "../integrations/airswap/IAirswapSwapERC20.sol"; // This contract is only for testing purposes -contract AirSwapERC20Mockup is ISwapERC20 { +contract AirswapERC20Mockup is IAirswapSwapERC20 { int16 public swapCalled = 0; address public recipient; diff --git a/tests/airswap/airswap-integration.js b/tests/airswap/airswap-integration.js index 19dd4e72c..0f465932d 100644 --- a/tests/airswap/airswap-integration.js +++ b/tests/airswap/airswap-integration.js @@ -1,8 +1,8 @@ const { expectRevert, expectEvent, BN, constants } = require("@openzeppelin/test-helpers"); const { artifacts } = require("hardhat"); -const AirSwapERC20Mockup = artifacts.require("AirSwapERC20Mockup"); -const AirSwapFeeConnector = artifacts.require("AirSwapFeeConnector"); +const AirswapERC20Mockup = artifacts.require("AirswapERC20Mockup"); +const AirswapFeeConnector = artifacts.require("AirswapFeeConnector"); const TestToken = artifacts.require("TestToken"); const { ZERO_ADDRESS } = constants; @@ -20,7 +20,7 @@ function bnToComparableNumber(bn) { contract("AirSwap Integration", (accounts) => { let deployerAddress, senderAddress, marketMakerAddress, recipientAddress, feeVaultAddress; let testToken1, testToken1Address, testToken2, testToken2Address; - let airSwapERC20Mockup, airSwapFeeConnector; + let airSwapERC20Mockup, airswapFeeConnector; let fakeAddress; describe.only("AirSwap Integration test", async () => { @@ -33,8 +33,8 @@ contract("AirSwap Integration", (accounts) => { feeVaultAddress, ] = accounts; - airSwapERC20Mockup = await AirSwapERC20Mockup.new(); - airSwapFeeConnector = await AirSwapFeeConnector.new(); + airSwapERC20Mockup = await AirswapERC20Mockup.new(); + airswapFeeConnector = await AirswapFeeConnector.new(); testToken1 = await TestToken.new("TST1", "TST1", 18, INITIAL_AMOUNT_BN); testToken1Address = testToken1.address; @@ -53,15 +53,15 @@ contract("AirSwap Integration", (accounts) => { const fakeFee = new BN(Math.floor(10 ^ (18 * Math.random))); it("owner only", async () => { await expectRevert( - airSwapFeeConnector.setInputFee(fakeFee, { from: senderAddress }), + airswapFeeConnector.setInputFee(fakeFee, { from: senderAddress }), "unauthorized" ); }); it("can be set", async () => { - const { tx } = await airSwapFeeConnector.setInputFee(fakeFee); - const fee = await airSwapFeeConnector.inputFeeInPoints(); + const { tx } = await airswapFeeConnector.setInputFee(fakeFee); + const fee = await airswapFeeConnector.inputFeeInPoints(); expect(fee.toString()).to.be.equal(fakeFee.toString()); - await expectEvent.inTransaction(tx, airSwapFeeConnector, "InputFeeChangedEvent", { + await expectEvent.inTransaction(tx, airswapFeeConnector, "InputFeeChangedEvent", { sender: deployerAddress, feeInPoints: fakeFee, }); @@ -72,15 +72,15 @@ contract("AirSwap Integration", (accounts) => { const fakeFee = new BN(Math.floor(10 ^ (18 * Math.random))); it("owner only", async () => { await expectRevert( - airSwapFeeConnector.setOutputFee(fakeFee, { from: senderAddress }), + airswapFeeConnector.setOutputFee(fakeFee, { from: senderAddress }), "unauthorized" ); }); it("can be set", async () => { - const { tx } = await airSwapFeeConnector.setOutputFee(fakeFee); - const fee = await airSwapFeeConnector.outputFeeInPoints(); + const { tx } = await airswapFeeConnector.setOutputFee(fakeFee); + const fee = await airswapFeeConnector.outputFeeInPoints(); expect(fee.toString()).to.be.equal(fakeFee.toString()); - await expectEvent.inTransaction(tx, airSwapFeeConnector, "OutputFeeChangedEvent", { + await expectEvent.inTransaction(tx, airswapFeeConnector, "OutputFeeChangedEvent", { sender: deployerAddress, feeInPoints: fakeFee, }); @@ -90,17 +90,17 @@ contract("AirSwap Integration", (accounts) => { describe("feeVaultAddress", async () => { it("owner only", async () => { await expectRevert( - airSwapFeeConnector.setFeeVaultAddress(fakeAddress, { from: senderAddress }), + airswapFeeConnector.setFeeVaultAddress(fakeAddress, { from: senderAddress }), "unauthorized" ); }); it("can be set", async () => { - const { tx } = await airSwapFeeConnector.setFeeVaultAddress(fakeAddress); - const address = await airSwapFeeConnector.feeVaultAddress(); + const { tx } = await airswapFeeConnector.setFeeVaultAddress(fakeAddress); + const address = await airswapFeeConnector.feeVaultAddress(); expect(address).to.be.equal(fakeAddress); await expectEvent.inTransaction( tx, - airSwapFeeConnector, + airswapFeeConnector, "FeeVaultAddressChangedEvent", { sender: deployerAddress, @@ -113,17 +113,17 @@ contract("AirSwap Integration", (accounts) => { describe("swapERC20Address", async () => { it("owner only", async () => { await expectRevert( - airSwapFeeConnector.setSwapERC20Address(fakeAddress, { from: senderAddress }), + airswapFeeConnector.setSwapERC20Address(fakeAddress, { from: senderAddress }), "unauthorized" ); }); it("can be set", async () => { - const { tx } = await airSwapFeeConnector.setSwapERC20Address(fakeAddress); - const address = await airSwapFeeConnector.swapERC20Address(); + const { tx } = await airswapFeeConnector.setSwapERC20Address(fakeAddress); + const address = await airswapFeeConnector.swapERC20Address(); expect(address).to.be.equal(fakeAddress); await expectEvent.inTransaction( tx, - airSwapFeeConnector, + airswapFeeConnector, "SwapERC20AddressChangedEvent", { sender: deployerAddress, @@ -151,10 +151,10 @@ contract("AirSwap Integration", (accounts) => { const expectedOutputFee = (outputAmount * outputFeePoints) / POINTS; const expectedOutputAmountAfterFee = outputAmount - expectedOutputFee; - await airSwapFeeConnector.setSwapERC20Address(airSwapERC20Mockup.address); - await airSwapFeeConnector.setFeeVaultAddress(feeVaultAddress); - await airSwapFeeConnector.setInputFee(inputFeePoints); - await airSwapFeeConnector.setOutputFee(outputFeePoints); + await airswapFeeConnector.setSwapERC20Address(airSwapERC20Mockup.address); + await airswapFeeConnector.setFeeVaultAddress(feeVaultAddress); + await airswapFeeConnector.setInputFee(inputFeePoints); + await airswapFeeConnector.setOutputFee(outputFeePoints); await airSwapERC20Mockup.reset(); function numberToBn(n) { @@ -163,12 +163,12 @@ contract("AirSwap Integration", (accounts) => { // first we need to approve await testToken1.approve( - airSwapFeeConnector.address, + airswapFeeConnector.address, numberToBn(totalInputAmount) ); // then we can convert - const { tx } = await airSwapFeeConnector.swap( + const { tx } = await airswapFeeConnector.swap( deployerAddress, recipientAddress, fakeNonce, @@ -183,7 +183,7 @@ contract("AirSwap Integration", (accounts) => { fakeS ); - await expectEvent.inTransaction(tx, airSwapFeeConnector, "SwapEvent", { + await expectEvent.inTransaction(tx, airswapFeeConnector, "SwapEvent", { sender: deployerAddress, recipient: recipientAddress, sendToken: testToken1Address, From ea4b922f06ab37ce043bf16ded00398048e6edd4 Mon Sep 17 00:00:00 2001 From: Tyrone Johnson Date: Thu, 7 Dec 2023 12:51:09 +0300 Subject: [PATCH 08/12] renaming AirSwapERC20Mockup part1 --- contracts/mockup/AirSwapERC20Mockup.sol | 67 ------------------------- 1 file changed, 67 deletions(-) delete mode 100644 contracts/mockup/AirSwapERC20Mockup.sol diff --git a/contracts/mockup/AirSwapERC20Mockup.sol b/contracts/mockup/AirSwapERC20Mockup.sol deleted file mode 100644 index c7f7176b7..000000000 --- a/contracts/mockup/AirSwapERC20Mockup.sol +++ /dev/null @@ -1,67 +0,0 @@ -pragma solidity 0.5.17; - -import "../openzeppelin/IERC20_.sol"; -import "../integrations/airswap/IAirswapSwapERC20.sol"; - -// This contract is only for testing purposes -contract AirswapERC20Mockup is IAirswapSwapERC20 { - int16 public swapCalled = 0; - - address public recipient; - uint256 public nonce; - uint256 public expiry; - address public signerWallet; - address public signerToken; - uint256 public signerAmount; - address public senderToken; - uint256 public senderAmount; - uint8 public v; - bytes32 public r; - bytes32 public s; - - function reset() public { - recipient = address(0); - nonce = 0; - expiry = 0; - signerWallet = address(0); - signerToken = address(0); - signerAmount = 0; - senderToken = address(0); - senderAmount = 0; - v = 0; - r = 0; - s = 0; - - swapCalled = 0; - } - - function swap( - address _recipient, - uint256 _nonce, - uint256 _expiry, - address _signerWallet, - address _signerToken, - uint256 _signerAmount, - address _senderToken, - uint256 _senderAmount, - uint8 _v, - bytes32 _r, - bytes32 _s - ) external { - recipient = _recipient; - nonce = _nonce; - expiry = _expiry; - signerWallet = _signerWallet; - signerToken = _signerToken; - signerAmount = _signerAmount; - senderToken = _senderToken; - senderAmount = _senderAmount; - v = _v; - r = _r; - s = _s; - - swapCalled++; - - IERC20_(_signerToken).transfer(_recipient, _signerAmount); - } -} From 5aec1b7362bb9dbf98fc5ea958aba7e1456671f5 Mon Sep 17 00:00:00 2001 From: Tyrone Johnson Date: Thu, 7 Dec 2023 12:54:50 +0300 Subject: [PATCH 09/12] renaming AirSwapERC20Mockup part2 --- contracts/mockup/AirswapERC20Mockup.sol | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 contracts/mockup/AirswapERC20Mockup.sol diff --git a/contracts/mockup/AirswapERC20Mockup.sol b/contracts/mockup/AirswapERC20Mockup.sol new file mode 100644 index 000000000..c7f7176b7 --- /dev/null +++ b/contracts/mockup/AirswapERC20Mockup.sol @@ -0,0 +1,67 @@ +pragma solidity 0.5.17; + +import "../openzeppelin/IERC20_.sol"; +import "../integrations/airswap/IAirswapSwapERC20.sol"; + +// This contract is only for testing purposes +contract AirswapERC20Mockup is IAirswapSwapERC20 { + int16 public swapCalled = 0; + + address public recipient; + uint256 public nonce; + uint256 public expiry; + address public signerWallet; + address public signerToken; + uint256 public signerAmount; + address public senderToken; + uint256 public senderAmount; + uint8 public v; + bytes32 public r; + bytes32 public s; + + function reset() public { + recipient = address(0); + nonce = 0; + expiry = 0; + signerWallet = address(0); + signerToken = address(0); + signerAmount = 0; + senderToken = address(0); + senderAmount = 0; + v = 0; + r = 0; + s = 0; + + swapCalled = 0; + } + + function swap( + address _recipient, + uint256 _nonce, + uint256 _expiry, + address _signerWallet, + address _signerToken, + uint256 _signerAmount, + address _senderToken, + uint256 _senderAmount, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external { + recipient = _recipient; + nonce = _nonce; + expiry = _expiry; + signerWallet = _signerWallet; + signerToken = _signerToken; + signerAmount = _signerAmount; + senderToken = _senderToken; + senderAmount = _senderAmount; + v = _v; + r = _r; + s = _s; + + swapCalled++; + + IERC20_(_signerToken).transfer(_recipient, _signerAmount); + } +} From 2b0f5b5e4c6a27857e28f3e3c6a54b4c2a00f5fd Mon Sep 17 00:00:00 2001 From: Night Owl Date: Wed, 13 Dec 2023 19:10:48 +0530 Subject: [PATCH 10/12] add enumerable map for makers --- .../airswap/EnumerableMakerSet.sol | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 contracts/integrations/airswap/EnumerableMakerSet.sol diff --git a/contracts/integrations/airswap/EnumerableMakerSet.sol b/contracts/integrations/airswap/EnumerableMakerSet.sol new file mode 100644 index 000000000..ebe0010fc --- /dev/null +++ b/contracts/integrations/airswap/EnumerableMakerSet.sol @@ -0,0 +1,143 @@ +pragma solidity ^0.5.0; + +import "./IAirswapFeeConnector.sol"; +/** + * @dev Based on Library for managing + * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive + * types. + * + * Sets have the following properties: + * + * - Elements are added, removed, and checked for existence in constant time + * (O(1)). + * - Elements are enumerated in O(n). No guarantees are made on the ordering. + * + * + * Include with `using EnumerableSet for EnumerableSet.AddressSet;`. + * + * _Available since v2.5.0._ + */ +library EnumerableMakerSet { + struct MakerSet { + // Position of the value in the `values` array, plus 1 because index 0 + // means a value is not in the set. + mapping(address => uint256) index; + IAirswapFeeConnector.Maker[] values; + } + + /** + * @dev Add a value to a set. O(1). + * Returns false if the value was already in the set. + */ + function add(MakerSet storage set, IAirswapFeeConnector.Maker memory value) internal returns (bool) { + if (!contains(set, value.signer)) { + set.index[value.signer] = set.values.push(value); + return true; + } else { + return false; + } + } + + /** + * @dev Removes a value from a set. O(1). + * Returns false if the value was not present in the set. + */ + function remove(MakerSet storage set, IAirswapFeeConnector.Maker memory value) internal returns (bool) { + if (contains(set, value.signer)) { + uint256 toDeleteIndex = set.index[value.signer] - 1; + uint256 lastIndex = set.values.length - 1; + + // If the element we're deleting is the last one, we can just remove it without doing a swap + if (lastIndex != toDeleteIndex) { + IAirswapFeeConnector.Maker memory lastValue = set.values[lastIndex]; + + // Move the last value to the index where the deleted value is + set.values[toDeleteIndex] = lastValue; + // Update the index for the moved value + set.index[lastValue.signer] = toDeleteIndex + 1; // All indexes are 1-based + } + + // Delete the index entry for the deleted value + delete set.index[value.signer]; + + // Delete the old entry for the moved value + set.values.pop(); + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(MakerSet storage set, address signer) internal view returns (bool) { + return set.index[signer] != 0; + } + + /** + * @dev Returns an array with all values in the set. O(N). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + + * WARNING: This function may run out of gas on large sets: use {length} and + * {get} instead in these cases. + */ + function enumerate(MakerSet storage set) internal view returns (IAirswapFeeConnector.Maker[] memory) { + IAirswapFeeConnector.Maker[] memory output = new IAirswapFeeConnector.Maker[](set.values.length); + for (uint256 i; i < set.values.length; i++) { + output[i] = set.values[i]; + } + return output; + } + + /** + * @dev Returns a chunk of array as recommended in enumerate() to avoid running of gas. + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + + * WARNING: This function may run out of gas on large sets: use {length} and + * {get} instead in these cases. + + * @param start start index of chunk + * @param count num of element to return; if count == 0 then returns all the elements from the @param start + */ + function enumerateChunk( + MakerSet storage set, + uint256 start, + uint256 count + ) internal view returns (IAirswapFeeConnector.Maker[] memory output) { + uint256 end = start + count; + require(end >= start, "addition overflow"); + end = (set.values.length < end || count == 0) ? set.values.length : end; + if (end == 0 || start >= end) { + return output; + } + + output = new IAirswapFeeConnector.Maker[](end - start); + for (uint256 i; i < end - start; i++) { + output[i] = set.values[i + start]; + } + return output; + } + + /** + * @dev Returns the number of elements on the set. O(1). + */ + function length(MakerSet storage set) internal view returns (uint256) { + return set.values.length; + } + + /** @dev Returns the element stored at position `index` in the set. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function get(MakerSet storage set, uint256 index) internal view returns (IAirswapFeeConnector.Maker memory) { + return set.values[index]; + } +} From e5a3bda293a6aed717b420702d7f06cb9d110373 Mon Sep 17 00:00:00 2001 From: Night Owl Date: Wed, 13 Dec 2023 19:28:34 +0530 Subject: [PATCH 11/12] update interface for fee connector --- .../airswap/IAirswapFeeConnector.sol | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/contracts/integrations/airswap/IAirswapFeeConnector.sol b/contracts/integrations/airswap/IAirswapFeeConnector.sol index a4d60645b..75b0a45cb 100644 --- a/contracts/integrations/airswap/IAirswapFeeConnector.sol +++ b/contracts/integrations/airswap/IAirswapFeeConnector.sol @@ -1,10 +1,79 @@ pragma solidity 0.5.17; +pragma experimental ABIEncoderV2; + /** * @title A proxy to the AirSwap ERC20 contract that collects fees before and after the conversion. * @author Derek Mattr dharkmattr@gmail.com */ interface IAirswapFeeConnector { + + /// Maker + struct Maker { + /// @dev name of the market maker + string makerName; + /// @dev address of the signer who signs the order + address signer; + /// @dev address of the maker to send the fees + address feeReceiver; + } + + /// @notice Return array of makers registered in the contract + /// @return Array of Maker objects + function getMakers() external view returns (Maker[] memory); + + /// @notice Returns the rfq amount after subtracting the fees + /// @param amount Amount provided by the user for trade + /// @param splitAmmFees This param indicates if the fee should be divided by half and sent to 2 pools + /// @return Quote amount for trade after deducting the fees + function getRfqAmount(uint256 amount, bool splitAmmFees) external view returns (uint256); + + /// @notice Checks if the token address taken from the RFQ order is valid + /// @param converter Address of the converter used to validate token + /// @param token Address of the token to validate + /// @param isSourceToken To specify if the token is source or destination + /// @return isTokenSupported Checks if the token is supported by Sovryn protocol + /// @return isConverterValid Checks if the provided converter address is valid + /// @return isTokenValid Checks if the token provided is valid + function isValidConverterToken(address converter, address token, bool isSourceToken) + external + view + returns (bool isTokenSupported, bool isConverterValid, bool isTokenValid); + + /// @notice Returns the total fee percentage in bps + /// @return Sum of all fee percentages in bps + function getTotalBps() external view returns(uint256); + + /// @notice This function can be used to add new maker details + /// @param maker Maker struct providing the details of maker to add + function addMaker(Maker calldata maker) external; + + /// @notice Removes the maker from the list + /// @param signer Address of the signer used by maker + function removeMaker(address signer) external; + + /// @notice Update the signer address of the maker + /// @param fromAddress Current address of the maker + /// @param toAddress New address of the maker + function updateMakerAddress(address fromAddress, address toAddress) external; + + /// @notice Update the airswap fee percentage + /// @param _airswapFeeBps Airswap fee percentage in basis points + function setAirswapFeeBps(uint256 _airswapFeeBps) external; + + /// @notice Update the maker fee percentage + /// @param _makerFeeBps Maker fee percentage in basis points + function setMakerFeeBps(uint256 _makerFeeBps) external; + + /// @notice Update the amm lp fee percentage + /// @param _ammFeeLpBps Amm Fee Lp in basis points + function setAmmLpFeeBps(uint256 _ammFeeLpBps) external; + + /// @notice Update the bitocracy fee percentage + /// @param _bitocracyFeeBps Bitocracy fee in basis points + function setBitocracyFeeBps(uint256 _bitocracyFeeBps) external; + + /// @notice Set the input fee in points, ie 25 means 2.5 percent. /// The input fee is collected on the sent tokens before /// the actual conversion. From 55cf236ebfaf842b34d598f4555157767bce632e Mon Sep 17 00:00:00 2001 From: Night Owl Date: Wed, 13 Dec 2023 19:36:02 +0530 Subject: [PATCH 12/12] remove old methods --- .../airswap/IAirswapFeeConnector.sol | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/contracts/integrations/airswap/IAirswapFeeConnector.sol b/contracts/integrations/airswap/IAirswapFeeConnector.sol index 75b0a45cb..b5fb89a53 100644 --- a/contracts/integrations/airswap/IAirswapFeeConnector.sol +++ b/contracts/integrations/airswap/IAirswapFeeConnector.sol @@ -73,26 +73,6 @@ interface IAirswapFeeConnector { /// @param _bitocracyFeeBps Bitocracy fee in basis points function setBitocracyFeeBps(uint256 _bitocracyFeeBps) external; - - /// @notice Set the input fee in points, ie 25 means 2.5 percent. - /// The input fee is collected on the sent tokens before - /// the actual conversion. - /// @param inputFeeInPoints The new fee in points - function setInputFee(uint256 inputFeeInPoints) external; - - /// @notice Set the out fee in points, ie 25 means 2.5 percent. - /// The output fee is collecte after the conversion. - /// @param outputFeeInPoints The new fee in points - function setOutputFee(uint256 outputFeeInPoints) external; - - /// @notice Set the address to which fees are sent - /// @param newAddress The new address - function setFeeVaultAddress(address newAddress) external; - - /// @notice Set the address of the AirSwap contract - /// @param newAddress The new address - function setSwapERC20Address(address newAddress) external; - /// @notice Swap one token for another. /// @param _sender Address which is sending the tokens /// @param _recipient Address to send the resulting tokens after collecting the output fee