-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add Uniswap integration Allow to buy ETH or any other token. * Add tests Reach 100% coverage. * Add Uniswap: fix fetching wrong exchange Address PR #3 comments. * Remove _from parameter from contributeExternalToken Address PR #3 comments. * Add extra-safety measures Make sure no funds are left in the contract. Address PR #3 comments. * fixup! Add extra-safety measures * Add check for non-existent Uniswap exchange * test: Fix name in aux variable * Add flag to signal activation of tokens in Registry Fixes #4. * Add integration for Phase 2 Allow to buy bonded tokens directly from Uniswap and stake them in the Registry. Fixes #5. * Remove unnecessary isClosed function from IPresale interface Address PR #9 comments. * Update README.md * UniswapWrapper: add msg.sender == _token check to receiveApproval() * Remove duplicated Uniswap contracts for testing * Add back Uniswap interfaces * Uniswap wrapper phase 2: add tests for receiveApproval wrong sender * Uniswap wrapper phase 2: add check for data length ... in receiveApproval * UniswapWrapper: update outdated / misleading comments * Add new refund mechanism to Uniswap wrapper And abstract it into a shared base contract. * Reuse refundable tests in subclasses * 1.3.0 Co-authored-by: Brett Sun <[email protected]>
- Loading branch information
Showing
10 changed files
with
638 additions
and
126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
pragma solidity ^0.5.8; | ||
|
||
import "@aragon/court/contracts/lib/os/ERC20.sol"; | ||
import "@aragon/court/contracts/lib/os/SafeERC20.sol"; | ||
|
||
|
||
contract Refundable { | ||
using SafeERC20 for ERC20; | ||
|
||
string private constant ERROR_NOT_GOVERNOR = "REF_NOT_GOVERNOR"; | ||
string private constant ERROR_ZERO_AMOUNT = "REF_ZERO_AMOUNT"; | ||
string private constant ERROR_NOT_ENOUGH_BALANCE = "REF_NOT_ENOUGH_BALANCE"; | ||
string private constant ERROR_ETH_REFUND = "REF_ETH_REFUND"; | ||
string private constant ERROR_TOKEN_REFUND = "REF_TOKEN_REFUND"; | ||
|
||
address public governor; | ||
|
||
modifier onlyGovernor() { | ||
require(msg.sender == governor, ERROR_NOT_GOVERNOR); | ||
_; | ||
} | ||
|
||
constructor(address _governor) public { | ||
governor = _governor; | ||
} | ||
|
||
/** | ||
* @notice Refunds accidentally sent ETH. Only governor can do it | ||
* @param _recipient Address to send funds to | ||
* @param _amount Amount to be refunded | ||
*/ | ||
function refundEth(address payable _recipient, uint256 _amount) external onlyGovernor { | ||
require(_amount > 0, ERROR_ZERO_AMOUNT); | ||
uint256 selfBalance = address(this).balance; | ||
require(selfBalance >= _amount, ERROR_NOT_ENOUGH_BALANCE); | ||
|
||
// solium-disable security/no-call-value | ||
(bool result,) = _recipient.call.value(_amount)(""); | ||
require(result, ERROR_ETH_REFUND); | ||
} | ||
|
||
/** | ||
* @notice Refunds accidentally sent ERC20 tokens. Only governor can do it | ||
* @param _token Token to be refunded | ||
* @param _recipient Address to send funds to | ||
* @param _amount Amount to be refunded | ||
*/ | ||
function refundToken(ERC20 _token, address _recipient, uint256 _amount) external onlyGovernor { | ||
require(_amount > 0, ERROR_ZERO_AMOUNT); | ||
uint256 selfBalance = _token.balanceOf(address(this)); | ||
require(selfBalance >= _amount, ERROR_NOT_ENOUGH_BALANCE); | ||
|
||
require(_token.safeTransfer(_recipient, _amount), ERROR_TOKEN_REFUND); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
pragma solidity ^0.5.8; | ||
|
||
import "@aragon/court/contracts/lib/os/IsContract.sol"; | ||
import "@aragon/court/contracts/lib/os/ERC20.sol"; | ||
import "@aragon/court/contracts/lib/os/SafeERC20.sol"; | ||
import "@aragon/court/contracts/standards/ERC900.sol"; | ||
import "./lib/uniswap/interfaces/IUniswapExchange.sol"; | ||
import "./lib/uniswap/interfaces/IUniswapFactory.sol"; | ||
import "./Refundable.sol"; | ||
|
||
|
||
contract UniswapWrapper is Refundable, IsContract { | ||
using SafeERC20 for ERC20; | ||
|
||
string private constant ERROR_TOKEN_NOT_CONTRACT = "UW_TOKEN_NOT_CONTRACT"; | ||
string private constant ERROR_REGISTRY_NOT_CONTRACT = "UW_REGISTRY_NOT_CONTRACT"; | ||
string private constant ERROR_UNISWAP_FACTORY_NOT_CONTRACT = "UW_UNISWAP_FACTORY_NOT_CONTRACT"; | ||
string private constant ERROR_RECEIVED_WRONG_TOKEN = "UW_RECEIVED_WRONG_TOKEN"; | ||
string private constant ERROR_WRONG_DATA_LENGTH = "UW_WRONG_DATA_LENGTH"; | ||
string private constant ERROR_ZERO_AMOUNT = "UW_ZERO_AMOUNT"; | ||
string private constant ERROR_TOKEN_TRANSFER_FAILED = "UW_TOKEN_TRANSFER_FAILED"; | ||
string private constant ERROR_TOKEN_APPROVAL_FAILED = "UW_TOKEN_APPROVAL_FAILED"; | ||
string private constant ERROR_UNISWAP_UNAVAILABLE = "UW_UNISWAP_UNAVAILABLE"; | ||
|
||
bytes32 internal constant ACTIVATE_DATA = keccak256("activate(uint256)"); | ||
|
||
ERC20 public bondedToken; | ||
ERC900 public registry; | ||
IUniswapFactory public uniswapFactory; | ||
|
||
constructor(address _governor, ERC20 _bondedToken, ERC900 _registry, IUniswapFactory _uniswapFactory) Refundable(_governor) public { | ||
require(isContract(address(_bondedToken)), ERROR_TOKEN_NOT_CONTRACT); | ||
require(isContract(address(_registry)), ERROR_REGISTRY_NOT_CONTRACT); | ||
require(isContract(address(_uniswapFactory)), ERROR_UNISWAP_FACTORY_NOT_CONTRACT); | ||
|
||
bondedToken = _bondedToken; | ||
registry = _registry; | ||
uniswapFactory = _uniswapFactory; | ||
} | ||
|
||
/** | ||
* @dev This function must be triggered by an external token's approve-and-call fallback. | ||
* It will pull the approved tokens and convert them in Uniswap, and activate the converted | ||
* tokens into a jurors registry instance of an Aragon Court. | ||
* @param _from Address of the original caller (juror) converting and activating the tokens | ||
* @param _amount Amount of external tokens to be converted and activated | ||
* @param _token Address of the external token triggering the approve-and-call fallback | ||
* @param _data Contains: | ||
* - 1st word: activate If non-zero, it will signal token activation in the registry | ||
* - 2nd word: minTokens Uniswap param | ||
* - 3rd word: minEth Uniswap param | ||
* - 4th word: deadline Uniswap param | ||
*/ | ||
function receiveApproval(address _from, uint256 _amount, address _token, bytes calldata _data) external { | ||
require(_token == msg.sender, ERROR_RECEIVED_WRONG_TOKEN); | ||
// data must have 4 words | ||
require(_data.length == 128, ERROR_WRONG_DATA_LENGTH); | ||
|
||
bool activate; | ||
uint256 minTokens; | ||
uint256 minEth; | ||
uint256 deadline; | ||
bytes memory data = _data; | ||
assembly { | ||
activate := mload(add(data, 0x20)) | ||
minTokens := mload(add(data, 0x40)) | ||
minEth := mload(add(data, 0x60)) | ||
deadline := mload(add(data, 0x80)) | ||
} | ||
|
||
_contributeExternalToken(_from, _amount, _token, minTokens, minEth, deadline, activate); | ||
} | ||
|
||
/** | ||
* @dev This function needs a previous approval on the external token used for the contributed amount. | ||
* It will pull the approved tokens, convert them in Uniswap to the bonded token, | ||
* and activate the converted tokens in the jurors registry instance of the Aragon Court. | ||
* @param _amount Amount of contribution tokens to be converted and activated | ||
* @param _token Address of the external contribution token used | ||
* @param _minTokens Minimum amount of bonded tokens obtained in Uniswap | ||
* @param _minEth Minimum amount of ETH obtained in Uniswap (Uniswap internally converts first to ETH and then to target token) | ||
* @param _deadline Transaction deadline for Uniswap | ||
* @param _activate Signal activation of tokens in the registry | ||
*/ | ||
function contributeExternalToken( | ||
uint256 _amount, | ||
address _token, | ||
uint256 _minTokens, | ||
uint256 _minEth, | ||
uint256 _deadline, | ||
bool _activate | ||
) | ||
external | ||
{ | ||
_contributeExternalToken(msg.sender, _amount, _token, _minTokens, _minEth, _deadline, _activate); | ||
} | ||
|
||
/** | ||
* @dev It will send the received ETH to Uniswap to get bonded tokens, | ||
* and activate the converted tokens in the jurors registry instance of the Aragon Court. | ||
* @param _minTokens Minimum amount of bonded tokens obtained in Uniswap | ||
* @param _deadline Transaction deadline for Uniswap | ||
* @param _activate Signal activation of tokens in the registry | ||
*/ | ||
function contributeEth(uint256 _minTokens, uint256 _deadline, bool _activate) external payable { | ||
require(msg.value > 0, ERROR_ZERO_AMOUNT); | ||
|
||
// get the Uniswap exchange for the bonded token | ||
address payable uniswapExchangeAddress = uniswapFactory.getExchange(address(bondedToken)); | ||
require(uniswapExchangeAddress != address(0), ERROR_UNISWAP_UNAVAILABLE); | ||
IUniswapExchange uniswapExchange = IUniswapExchange(uniswapExchangeAddress); | ||
|
||
// swap tokens | ||
uint256 bondedTokenAmount = uniswapExchange.ethToTokenSwapInput.value(msg.value)(_minTokens, _deadline); | ||
|
||
// stake and activate in the registry | ||
_stakeAndActivate(msg.sender, bondedTokenAmount, _activate); | ||
} | ||
|
||
function _contributeExternalToken( | ||
address _from, | ||
uint256 _amount, | ||
address _token, | ||
uint256 _minTokens, | ||
uint256 _minEth, | ||
uint256 _deadline, | ||
bool _activate | ||
) | ||
internal | ||
{ | ||
require(_amount > 0, ERROR_ZERO_AMOUNT); | ||
|
||
// move tokens to this contract | ||
ERC20 token = ERC20(_token); | ||
require(token.safeTransferFrom(_from, address(this), _amount), ERROR_TOKEN_TRANSFER_FAILED); | ||
|
||
// get the Uniswap exchange for the external token | ||
address payable uniswapExchangeAddress = uniswapFactory.getExchange(_token); | ||
require(uniswapExchangeAddress != address(0), ERROR_UNISWAP_UNAVAILABLE); | ||
IUniswapExchange uniswapExchange = IUniswapExchange(uniswapExchangeAddress); | ||
|
||
require(token.safeApprove(address(uniswapExchange), _amount), ERROR_TOKEN_APPROVAL_FAILED); | ||
|
||
// swap tokens | ||
uint256 bondedTokenAmount = uniswapExchange.tokenToTokenSwapInput(_amount, _minTokens, _minEth, _deadline, address(bondedToken)); | ||
|
||
// stake and activate in the registry | ||
_stakeAndActivate(_from, bondedTokenAmount, _activate); | ||
} | ||
|
||
function _stakeAndActivate(address _from, uint256 _amount, bool _activate) internal { | ||
// activate in registry | ||
bondedToken.approve(address(registry), _amount); | ||
bytes memory data; | ||
if (_activate) { | ||
data = abi.encodePacked(ACTIVATE_DATA); | ||
} | ||
registry.stakeFor(_from, _amount, data); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.