Skip to content
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

Closed
59 changes: 58 additions & 1 deletion packages/protocol/contracts/common/FeeCurrencyWhitelist.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ contract FeeCurrencyWhitelist is
{
Copy link
Contributor Author

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.

// 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;
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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 ..

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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 setUnderlyingToken and instead set the underlying token in setAdapter

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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);
}

/**
Expand Down
226 changes: 226 additions & 0 deletions packages/protocol/test-sol/common/FeeCurrencyWhitelist.t.sol
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);
}
}
75 changes: 0 additions & 75 deletions packages/protocol/test/common/feecurrencywhitelist.ts

This file was deleted.

Loading