-
Notifications
You must be signed in to change notification settings - Fork 369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added adapters mappings and helpers #10910
Changes from 10 commits
ad6c956
5dd80a9
d6ebd5f
04d055a
f491391
4d39bb6
3c72fa6
d51a58f
7ffdbba
89cdd27
bea8ee7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,10 +20,17 @@ contract FeeCurrencyWhitelist is | |
{ | ||
// Array of all the tokens enabled | ||
address[] public whitelist; | ||
// it is not enforce that underlyingTokens in the same order as their respective | ||
// whitelisted address | ||
address[] public underlyingTokens; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could make this AddressSet which would allow to add underlyingTokens directly in setAdaptor rather than have special method for it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will not work if we want to have multiple adapters for one token .. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather not update this to 0.8 and leave as is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem with current approach is that it is rather manual. You can setAdapter and you can forget to setUnderlyingToken which will result in inconsistent behaviour. If we will introduce AddressSet instead of array, then we can remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can make setAdapter also set the underlying token, so it's one call and there's no chance for error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And regarding this will not work for more than a token, yes, that's correct. But in reality I don't see how we could have more than one adaptor for a token, |
||
mapping(address => address) public adapters; | ||
|
||
event FeeCurrencyWhitelisted(address token); | ||
|
||
event FeeCurrencyWhitelistRemoved(address token); | ||
event AdapterSet(address underlyingToken, address adapter); | ||
event UnderlyingTokenSet(address underlyingToken); | ||
event UnderlyingTokenRemoved(address underlyingToken); | ||
|
||
/** | ||
* @notice Sets initialized == true on implementation contracts | ||
|
@@ -46,7 +53,57 @@ contract FeeCurrencyWhitelist is | |
* @return Patch version of the contract. | ||
*/ | ||
function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { | ||
return (1, 1, 1, 0); | ||
return (1, 1, 2, 0); | ||
} | ||
|
||
// removed with address zero | ||
function setAdapter(address token, address adapter) external onlyOwner { | ||
adapters[token] = adapter; | ||
emit AdapterSet(token, adapter); | ||
} | ||
|
||
// TODO make external | ||
function getAdapter(address underlyingToken) public view returns (address) { | ||
address result = adapters[underlyingToken]; | ||
return (result != address(0)) ? result : underlyingToken; | ||
} | ||
|
||
function removeAdapter(address token) external onlyOwner { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be called when creating the whitelist, not on-by-one |
||
delete adapters[token]; | ||
emit AdapterSet(token, address(0)); | ||
} | ||
|
||
function setUnderlyingToken(address tokenAddress) external onlyOwner() { | ||
underlyingTokens.push(tokenAddress); | ||
emit UnderlyingTokenSet(tokenAddress); | ||
} | ||
|
||
function getUnderlyingTokens() external view returns (address[] memory) { | ||
return underlyingTokens; | ||
} | ||
|
||
// TODO when this contracts gets moved to Solidity 0.8 it should | ||
// return an array of tuples | ||
// sequence of underlying token and it's corresponding adapter | ||
function getWhitelistUnderlingPairs() external view returns (address[] memory, address[] memory) { | ||
uint256 length = underlyingTokens.length; | ||
address[] memory tokensRes = new address[](length); | ||
address[] memory adaptersRes = new address[](length); | ||
|
||
for (uint256 i = 0; i < underlyingTokens.length; i++) { | ||
address underlyingToken = underlyingTokens[i]; | ||
tokensRes[i] = underlyingToken; | ||
adaptersRes[i] = getAdapter(underlyingToken); | ||
} | ||
return (tokensRes, adaptersRes); | ||
} | ||
|
||
function removeUnderlyingTokens(address tokenAddress, uint256 index) external onlyOwner { | ||
require(underlyingTokens[index] == tokenAddress, "Index does not match"); | ||
uint256 length = underlyingTokens.length; | ||
underlyingTokens[index] = underlyingTokens[length - 1]; | ||
underlyingTokens.pop(); | ||
emit UnderlyingTokenRemoved(tokenAddress); | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.5.13; | ||
|
||
import "celo-foundry/Test.sol"; | ||
import "../../contracts/common/FeeCurrencyWhitelist.sol"; | ||
|
||
contract FeeCurrencyWhitelistTest is Test { | ||
FeeCurrencyWhitelist feeCurrencyWhitelist; | ||
address nonOwner; | ||
address owner; | ||
|
||
function setUp() public { | ||
owner = address(this); | ||
nonOwner = actor("nonOwner"); | ||
|
||
feeCurrencyWhitelist = new FeeCurrencyWhitelist(true); | ||
feeCurrencyWhitelist.initialize(); | ||
} | ||
} | ||
|
||
contract FeeCurrencyWhitelistInitialize is FeeCurrencyWhitelistTest { | ||
function test_InitializeOwner() public { | ||
assertTrue(feeCurrencyWhitelist.isOwner()); | ||
assertEq(feeCurrencyWhitelist.owner(), address(this)); | ||
} | ||
|
||
function test_ShouldNotBeCallableAgain() public { | ||
vm.expectRevert("contract already initialized"); | ||
feeCurrencyWhitelist.initialize(); | ||
} | ||
} | ||
|
||
contract FeeCurrencyWhitelist_adaptedTokens is FeeCurrencyWhitelistTest { | ||
address whitelistedAddress1 = address(1); | ||
address whitelistedAddress2 = address(2); | ||
address whitelistedAddress3 = address(3); | ||
|
||
address token1 = address(4); | ||
address token2 = address(5); | ||
|
||
function setUp() public { | ||
super.setUp(); | ||
|
||
feeCurrencyWhitelist.addToken(whitelistedAddress1); // real token | ||
feeCurrencyWhitelist.addToken(whitelistedAddress2); // adaptor of token 1 | ||
feeCurrencyWhitelist.addToken(whitelistedAddress3); // adaptor of token 2 | ||
|
||
feeCurrencyWhitelist.setUnderlyingToken(whitelistedAddress1); | ||
feeCurrencyWhitelist.setUnderlyingToken(token1); | ||
feeCurrencyWhitelist.setUnderlyingToken(token2); | ||
|
||
feeCurrencyWhitelist.setAdapter(token1, whitelistedAddress2); | ||
} | ||
} | ||
|
||
contract FeeCurrencyWhitelist_setAdapter is FeeCurrencyWhitelist_adaptedTokens { | ||
event AdapterSet(address underlyingToken, address adapter); | ||
function test_geTokenForVanillaGasCurrency() public { | ||
// should be the same, it doesn't require adapter | ||
assertEq(feeCurrencyWhitelist.getAdapter(whitelistedAddress1), whitelistedAddress1); | ||
} | ||
|
||
function test_geTokenForAdaptedGasCurrency() public { | ||
// should be the same, it doesn't require adapter | ||
assertEq(feeCurrencyWhitelist.getAdapter(token1), whitelistedAddress2); | ||
} | ||
|
||
function test_Reverts_WhenNonOwnerAddsAToken() public { | ||
vm.expectRevert("Ownable: caller is not the owner"); | ||
vm.prank(nonOwner); | ||
feeCurrencyWhitelist.setAdapter(token2, whitelistedAddress3); | ||
} | ||
|
||
function test_ShouldEmitAdapterSet() public { | ||
vm.expectEmit(true, true, true, true); | ||
emit AdapterSet(token2, whitelistedAddress3); | ||
feeCurrencyWhitelist.setAdapter(token2, whitelistedAddress3); | ||
} | ||
} | ||
|
||
contract FeeCurrencyWhitelist_removeAdapter is FeeCurrencyWhitelist_adaptedTokens { | ||
event AdapterSet(address underlyingToken, address adapter); | ||
|
||
function test_ShouldRemoveNonExistentAdapter() public { | ||
feeCurrencyWhitelist.removeAdapter(token1); | ||
assertEq(feeCurrencyWhitelist.getAdapter(token1), token1); | ||
} | ||
|
||
function test_ShouldRemoveAdapter() public { | ||
feeCurrencyWhitelist.setAdapter(token1, whitelistedAddress2); | ||
assertEq(feeCurrencyWhitelist.getAdapter(token1), whitelistedAddress2); | ||
feeCurrencyWhitelist.removeAdapter(token1); | ||
assertEq(feeCurrencyWhitelist.getAdapter(token1), token1); | ||
} | ||
|
||
function test_ShouldEmitAdapterSet_WhenAdapterRemoved() public { | ||
vm.expectEmit(true, true, true, true); | ||
emit AdapterSet(token1, address(0)); | ||
feeCurrencyWhitelist.removeAdapter(token1); | ||
} | ||
|
||
} | ||
|
||
contract FeeCurrencyWhitelist_setUnderlyingTokens is FeeCurrencyWhitelist_adaptedTokens { | ||
event UnderlyingTokenSet(address underlyingToken); | ||
|
||
function test_ShouldReturnUnderlyingToken() external { | ||
address[3] memory addresses = [whitelistedAddress1, token1, token2]; | ||
address[] memory results = feeCurrencyWhitelist.getUnderlyingTokens(); | ||
|
||
// TODO forge has a helper for comparing arrays? | ||
assertEq(results.length, addresses.length); | ||
for (uint256 i = 0; i < results.length; i++) { | ||
assertEq(results[i], addresses[i]); | ||
} | ||
} | ||
|
||
function test_ShouldEmitUnderlyingTokenSet() public { | ||
vm.expectEmit(true, true, true, true); | ||
emit UnderlyingTokenSet(token1); | ||
feeCurrencyWhitelist.setUnderlyingToken(token1); | ||
} | ||
|
||
function test_Reverts_WhenNonOwnerSetsUnderlyingTokens() public { | ||
vm.expectRevert("Ownable: caller is not the owner"); | ||
vm.prank(nonOwner); | ||
feeCurrencyWhitelist.setUnderlyingToken(token1); | ||
} | ||
} | ||
|
||
contract FeeCurrencyWhitelist_removeUnderlyingTokens is FeeCurrencyWhitelist_adaptedTokens { | ||
event UnderlyingTokenRemoved(address underlyingToken); | ||
|
||
function test_ShouldRemoveToken() public { | ||
feeCurrencyWhitelist.removeUnderlyingTokens(token1, 1); | ||
address[] memory result = feeCurrencyWhitelist.getUnderlyingTokens(); | ||
assertEq(result.length, 2); | ||
assertEq(result[0], whitelistedAddress1); | ||
assertEq(result[1], token2); | ||
} | ||
|
||
function test_ShouldEmitUnderlyingTokenRemoved() public { | ||
vm.expectEmit(true, true, true, true); | ||
emit UnderlyingTokenRemoved(token1); | ||
feeCurrencyWhitelist.removeUnderlyingTokens(token1, 1); | ||
} | ||
|
||
function test_ShouldRevert_WhenIndexIsWrong() public { | ||
vm.expectRevert("Index does not match"); | ||
feeCurrencyWhitelist.removeUnderlyingTokens(token1, 2); | ||
} | ||
|
||
function test_ShouldRevert_WhenNonOwnerRemovesToken() public { | ||
vm.expectRevert("Ownable: caller is not the owner"); | ||
vm.prank(nonOwner); | ||
feeCurrencyWhitelist.removeUnderlyingTokens(token1, 2); | ||
} | ||
} | ||
|
||
contract FeeCurrencyWhitelist_getWhitelistUnderlingPairs is FeeCurrencyWhitelist_adaptedTokens { | ||
function test_getsRightTuples() external { | ||
feeCurrencyWhitelist.setAdapter(token2, whitelistedAddress3); | ||
address[3] memory expectedTokens = [whitelistedAddress1, token1, token2]; | ||
|
||
address[3] memory expectedAdapters = [ | ||
whitelistedAddress1, | ||
whitelistedAddress2, | ||
whitelistedAddress3 | ||
]; | ||
|
||
(address[] memory tokens, address[] memory adapters) = feeCurrencyWhitelist | ||
.getWhitelistUnderlingPairs(); | ||
|
||
assertEq(tokens.length, expectedTokens.length); | ||
assertEq(adapters.length, expectedTokens.length); | ||
for (uint256 i = 0; i < tokens.length; i++) { | ||
assertEq(tokens[i], expectedTokens[i]); | ||
assertEq(adapters[i], expectedAdapters[i]); | ||
} | ||
} | ||
} | ||
|
||
contract FeeCurrencyWhitelistAddToken is FeeCurrencyWhitelistTest { | ||
function test_ShouldAllowTheOwnerToAddAToken() public { | ||
feeCurrencyWhitelist.addToken(address(1)); | ||
address[] memory whitelist = feeCurrencyWhitelist.getWhitelist(); | ||
assertEq(whitelist.length, 1); | ||
assertEq(whitelist[0], address(1)); | ||
} | ||
|
||
function test_ShouldRevert_WhenNonOwnerAddsAToken() public { | ||
vm.expectRevert("Ownable: caller is not the owner"); | ||
vm.prank(nonOwner); | ||
feeCurrencyWhitelist.addToken(address(1)); | ||
} | ||
} | ||
|
||
contract FeeCurrencyWhitelistRemoveToken is FeeCurrencyWhitelistTest { | ||
function setUp() public { | ||
super.setUp(); | ||
feeCurrencyWhitelist.addToken(address(1)); | ||
feeCurrencyWhitelist.addToken(address(2)); | ||
feeCurrencyWhitelist.addToken(address(3)); | ||
} | ||
|
||
function test_ShouldRemoveToken() public { | ||
feeCurrencyWhitelist.removeToken(address(2), 1); | ||
address[] memory whitelist = feeCurrencyWhitelist.getWhitelist(); | ||
assertEq(whitelist.length, 2); | ||
assertEq(whitelist[0], address(1)); | ||
assertEq(whitelist[1], address(3)); | ||
} | ||
|
||
// TODO this test should check emits | ||
|
||
function test_ShouldRevert_WhenIndexIsWrong() public { | ||
vm.expectRevert("Index does not match"); | ||
feeCurrencyWhitelist.removeToken(address(2), 2); | ||
} | ||
|
||
function test_ShouldRevert_WhenNonOwnerRemovesToken() public { | ||
vm.expectRevert("Ownable: caller is not the owner"); | ||
vm.prank(nonOwner); | ||
feeCurrencyWhitelist.removeToken(address(2), 1); | ||
} | ||
} |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should update interface as well.