diff --git a/packages/protocol/contracts/common/FeeHandler.sol b/packages/protocol/contracts/common/FeeHandler.sol index 381fe39cafc..35ef8dceeb1 100644 --- a/packages/protocol/contracts/common/FeeHandler.sol +++ b/packages/protocol/contracts/common/FeeHandler.sol @@ -19,7 +19,6 @@ import "../common/interfaces/IFeeHandlerSeller.sol"; import "./interfaces/IStableTokenMento.sol"; import "../common/interfaces/ICeloVersionedContract.sol"; import "../common/interfaces/ICeloToken.sol"; -import "../stability/interfaces/ISortedOracles.sol"; // Using the minimal required signatures in the interfaces so more contracts could be compatible import "../common/libraries/ReentrancyGuard.sol"; @@ -66,6 +65,8 @@ contract FeeHandler is // last day the daily limits were updated uint256 private deprecated_lastLimitDay; // deprecated + // reason it's inverse it's because it used to be burnFraction and was migrated + // ignoreRenaming_ prefix allows the tooling to ignore the variable renaming FixidityLib.Fraction private ignoreRenaming_inverseCarbonFraction; // 80% address public ignoreRenaming_carbonFeeBeneficiary; diff --git a/packages/protocol/contracts/common/FeeHandlerSeller.sol b/packages/protocol/contracts/common/FeeHandlerSeller.sol index 8ccd03b08ef..24d703c1d02 100644 --- a/packages/protocol/contracts/common/FeeHandlerSeller.sol +++ b/packages/protocol/contracts/common/FeeHandlerSeller.sol @@ -1,24 +1,28 @@ pragma solidity ^0.5.13; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "../common/FixidityLib.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + import "./UsingRegistry.sol"; +import "../common/FixidityLib.sol"; import "../common/Initializable.sol"; +import "../common/interfaces/ICeloVersionedContract.sol"; // Abstract class for a FeeHandlerSeller, as defined in CIP-52 // https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md -contract FeeHandlerSeller is Ownable, Initializable, UsingRegistry { +contract FeeHandlerSeller is Ownable, Initializable, UsingRegistry, ICeloVersionedContract { using SafeMath for uint256; using FixidityLib for FixidityLib.Fraction; // Address of the token // Minimal number of reports in SortedOracles contract mapping(address => uint256) public minimumReports; + mapping(address => address) public oracleAddresses; event MinimumReportsSet(address tokenAddress, uint256 minimumReports); event TokenSold(address soldTokenAddress, address boughtTokenAddress, uint256 amount); + event OracleAddressSet(address _token, address _oracle); function initialize( address _registryAddress, @@ -87,4 +91,18 @@ contract FeeHandlerSeller is Ownable, Initializable, UsingRegistry { minimumReports[tokenAddress] = newMininumReports; emit MinimumReportsSet(tokenAddress, newMininumReports); } + + function setOracleAddress(address _tokenAddress, address _oracleAddress) external onlyOwner { + oracleAddresses[_tokenAddress] = _oracleAddress; + emit OracleAddressSet(_tokenAddress, _oracleAddress); + } + + function getOracleAddress(address _tokenAddress) public view returns (address) { + address oracleAddress = oracleAddresses[_tokenAddress]; + if (oracleAddress != address(0)) { + return oracleAddress; + } + + return address(getSortedOracles()); + } } diff --git a/packages/protocol/contracts/common/MentoFeeHandlerSeller.sol b/packages/protocol/contracts/common/MentoFeeHandlerSeller.sol index bdabe21b230..c462c2ff637 100644 --- a/packages/protocol/contracts/common/MentoFeeHandlerSeller.sol +++ b/packages/protocol/contracts/common/MentoFeeHandlerSeller.sol @@ -30,6 +30,7 @@ contract MentoFeeHandlerSeller is IFeeHandlerSeller, FeeHandlerSeller { // without this line the contract can't receive native Celo transfers function() external payable {} + // Note: current version of Mento is not compatible with this Seller function sell( address sellTokenAddress, address buyTokenAddress, @@ -80,6 +81,6 @@ contract MentoFeeHandlerSeller is IFeeHandlerSeller, FeeHandlerSeller { * @return Patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 0, 1); + return (1, 1, 1, 0); } } diff --git a/packages/protocol/contracts/common/UniswapFeeHandlerSeller.sol b/packages/protocol/contracts/common/UniswapFeeHandlerSeller.sol index 66166880087..72feb565126 100644 --- a/packages/protocol/contracts/common/UniswapFeeHandlerSeller.sol +++ b/packages/protocol/contracts/common/UniswapFeeHandlerSeller.sol @@ -10,6 +10,7 @@ import "./UsingRegistry.sol"; import "../common/interfaces/IFeeHandlerSeller.sol"; import "../stability/interfaces/ISortedOracles.sol"; +import "../../contracts-0.8/common/interfaces/IOracle.sol"; import "../common/FixidityLib.sol"; import "../common/Initializable.sol"; import "./FeeHandlerSeller.sol"; @@ -147,7 +148,7 @@ contract UniswapFeeHandlerSeller is IFeeHandlerSeller, FeeHandlerSeller { * @return Patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 1, 0, 1); + return (2, 0, 0, 0); } function _setRouter(address token, address router) private { @@ -175,18 +176,22 @@ contract UniswapFeeHandlerSeller is IFeeHandlerSeller, FeeHandlerSeller { uint256 amount, IUniswapV2RouterMin bestRouter ) private view returns (uint256) { - ISortedOracles sortedOracles = getSortedOracles(); + address _oracleAddress = getOracleAddress(sellTokenAddress); + uint256 minReports = minimumReports[sellTokenAddress]; - require( - sortedOracles.numRates(sellTokenAddress) >= minReports, - "Number of reports for token not enough" - ); + IOracle oracle = IOracle(_oracleAddress); uint256 minimalSortedOracles = 0; // if minimumReports for this token is zero, assume the check is not needed if (minReports > 0) { - (uint256 rateNumerator, uint256 rateDenominator) = sortedOracles.medianRate(sellTokenAddress); + ISortedOracles sortedOracles = ISortedOracles(_oracleAddress); + require( + sortedOracles.numRates(sellTokenAddress) >= minReports, + "Number of reports for token not enough" + ); + + (uint256 rateNumerator, uint256 rateDenominator) = oracle.getExchangeRate(sellTokenAddress); minimalSortedOracles = calculateMinAmount( rateNumerator, diff --git a/packages/protocol/contracts/stability/test/MockSortedOracles.sol b/packages/protocol/contracts/stability/test/MockSortedOracles.sol index 2a18a3c85d9..5c212954acd 100644 --- a/packages/protocol/contracts/stability/test/MockSortedOracles.sol +++ b/packages/protocol/contracts/stability/test/MockSortedOracles.sol @@ -1,9 +1,10 @@ pragma solidity >=0.5.13 <0.9.0; +import "../../../contracts-0.8/common/interfaces/IOracle.sol"; /** * @title A mock SortedOracles for testing. */ -contract MockSortedOracles { +contract MockSortedOracles is IOracle { uint256 public constant DENOMINATOR = 1000000000000000000000000; mapping(address => uint256) public numerators; mapping(address => uint256) public medianTimestamp; diff --git a/packages/protocol/lib/compatibility/utils.ts b/packages/protocol/lib/compatibility/utils.ts index e051619c25f..fb640c71be2 100644 --- a/packages/protocol/lib/compatibility/utils.ts +++ b/packages/protocol/lib/compatibility/utils.ts @@ -1,11 +1,11 @@ -import { reportASTIncompatibilities } from '@celo/protocol/lib/compatibility/ast-code' -import { reportLayoutIncompatibilities } from '@celo/protocol/lib/compatibility/ast-layout' -import { Categorizer } from '@celo/protocol/lib/compatibility/categorizer' -import { reportLibraryLinkingIncompatibilities } from '@celo/protocol/lib/compatibility/library-linking' -import { ASTDetailedVersionedReport, ASTReports } from '@celo/protocol/lib/compatibility/report' -import { linkedLibraries } from '@celo/protocol/migrationsConfig' -import { BuildArtifacts, Contracts, getBuildArtifacts } from '@openzeppelin/upgrades' -import { readJsonSync } from 'fs-extra' +import { reportASTIncompatibilities } from '@celo/protocol/lib/compatibility/ast-code'; +import { reportLayoutIncompatibilities } from '@celo/protocol/lib/compatibility/ast-layout'; +import { Categorizer } from '@celo/protocol/lib/compatibility/categorizer'; +import { reportLibraryLinkingIncompatibilities } from '@celo/protocol/lib/compatibility/library-linking'; +import { ASTDetailedVersionedReport, ASTReports } from '@celo/protocol/lib/compatibility/report'; +import { linkedLibraries } from '@celo/protocol/migrationsConfig'; +import { BuildArtifacts, Contracts, getBuildArtifacts } from '@openzeppelin/upgrades'; +import { readJsonSync } from 'fs-extra'; @@ -16,7 +16,7 @@ import { readJsonSync } from 'fs-extra' export class ASTBackwardReport { static create = ( - oldArtifactsFolder: string, + oldArtifactsFolders: string[], newArtifactsFolders: string[], oldArtifacts: BuildArtifacts[], newArtifacts: BuildArtifacts[], @@ -44,14 +44,14 @@ export class ASTBackwardReport { logFunction("Done\n") return new ASTBackwardReport( - oldArtifactsFolder, + oldArtifactsFolders, newArtifactsFolders, exclude.toString(), versionedReport) } constructor( - public readonly oldArtifactsFolder: string, + public readonly oldArtifactsFolder: string[], public readonly newArtifactsFolder: string[], public readonly exclude: string, public readonly report: ASTDetailedVersionedReport diff --git a/packages/protocol/releaseData/initializationData/release12.json b/packages/protocol/releaseData/initializationData/release12.json index 1b67eec0e45..3f5dad7b26b 100644 --- a/packages/protocol/releaseData/initializationData/release12.json +++ b/packages/protocol/releaseData/initializationData/release12.json @@ -3,5 +3,6 @@ "EpochManager": ["0x000000000000000000000000000000000000ce10", 86400], "EpochManagerEnabler": ["0x000000000000000000000000000000000000ce10"], "ScoreManager": [], - "FeeCurrencyDirectory": [] + "FeeCurrencyDirectory": [], + "UniswapFeeHandlerSeller": ["0x000000000000000000000000000000000000ce10", [], []] } diff --git a/packages/protocol/scripts/bash/contract-exclusion-regex.sh b/packages/protocol/scripts/bash/contract-exclusion-regex.sh index c233d4aa287..207684a2e79 100644 --- a/packages/protocol/scripts/bash/contract-exclusion-regex.sh +++ b/packages/protocol/scripts/bash/contract-exclusion-regex.sh @@ -8,6 +8,8 @@ CONTRACT_EXCLUSION_REGEX=".*Test|Mock.*|I[A-Z].*|.*Proxy|MultiSig.*|ReleaseGold| # Before CR7, UsingRegistry and UsingRegistryV2 had been deployed, they need to keep getting deployed to keep the release reports without changes. VERSION_NUMBER=$(echo "$BRANCH" | tr -dc '0-9') +echo "VERSION_NUMBER: $VERSION_NUMBER" + if [ $VERSION_NUMBER -gt 6 ] then CONTRACT_EXCLUSION_REGEX="$CONTRACT_EXCLUSION_REGEX|^UsingRegistry" @@ -23,3 +25,15 @@ if [ $VERSION_NUMBER -eq 9 ] then CONTRACT_EXCLUSION_REGEX="$CONTRACT_EXCLUSION_REGEX|SortedOracles" fi + +if [ $VERSION_NUMBER -eq 11 ] + then + # FeeHandlerSeller is not deployed, only its children + CONTRACT_EXCLUSION_REGEX="$CONTRACT_EXCLUSION_REGEX|\\bFeeHandlerSeller\\b" +fi + +if [ $VERSION_NUMBER -eq 12 ] + then + # FeeHandlerSeller is not deployed, only its children + CONTRACT_EXCLUSION_REGEX="$CONTRACT_EXCLUSION_REGEX|\\bFeeHandlerSeller\\b" +fi diff --git a/packages/protocol/scripts/bash/release-on-devchain.sh b/packages/protocol/scripts/bash/release-on-devchain.sh index e1662cbdd8f..56e465b2a03 100755 --- a/packages/protocol/scripts/bash/release-on-devchain.sh +++ b/packages/protocol/scripts/bash/release-on-devchain.sh @@ -53,7 +53,7 @@ echo " - Checkout migrationsConfig.js at $BRANCH" git checkout $BRANCH -- migrationsConfig.js source scripts/bash/contract-exclusion-regex.sh -yarn ts-node scripts/check-backward.ts sem_check --old_contracts $BUILD_DIR/contracts --new_contracts build/contracts --exclude $CONTRACT_EXCLUSION_REGEX --output_file report.json +yarn ts-node scripts/check-backward.ts sem_check --old_contracts $BUILD_DIR/contracts --new_contracts build/contracts --exclude $CONTRACT_EXCLUSION_REGEX --new_branch $BRANCH --output_file report.json echo "- Clean git modified file" git restore migrationsConfig.js diff --git a/packages/protocol/scripts/check-backward.ts b/packages/protocol/scripts/check-backward.ts index 1c77ca098ed..b671a25492b 100644 --- a/packages/protocol/scripts/check-backward.ts +++ b/packages/protocol/scripts/check-backward.ts @@ -62,6 +62,7 @@ const oldArtifactsFolder08 = path.relative(process.cwd(), argv.old_contracts + ' const newArtifactsFolder = path.relative(process.cwd(), argv.new_contracts) const newArtifactsFolder08 = path.relative(process.cwd(), argv.new_contracts + '-0.8') const newArtifactsFolders = [newArtifactsFolder, newArtifactsFolder08] +const oldArtifactsFolders = [oldArtifactsFolder, oldArtifactsFolder08] const out = (msg: string, force?: boolean): void => { if (force || !argv.quiet) { @@ -79,7 +80,7 @@ const newArtifacts08 = instantiateArtifacts(newArtifactsFolder08) try { const backward = ASTBackwardReport.create( - oldArtifactsFolder, + oldArtifactsFolders, newArtifactsFolders, [oldArtifacts, oldArtifacts08], [newArtifacts, newArtifacts08], diff --git a/packages/protocol/test-sol/unit/common/FeeHandler.t.sol b/packages/protocol/test-sol/unit/common/FeeHandler.t.sol index 5180f4b08a3..582e9cd15a9 100644 --- a/packages/protocol/test-sol/unit/common/FeeHandler.t.sol +++ b/packages/protocol/test-sol/unit/common/FeeHandler.t.sol @@ -1213,6 +1213,3 @@ contract FeeHandlerTest_SetBeneficiaryName is FeeHandlerTestAbstract { feeHandler.setBeneficiaryName(op, "OP revenue share updated"); } } - -// Mento doesn't need this sibce -// TODO add sets setting the oracle diff --git a/packages/protocol/test-sol/unit/common/FeeHandlerSeller.t.sol b/packages/protocol/test-sol/unit/common/FeeHandlerSeller.t.sol index e657af8e6f8..c9abbafc70f 100644 --- a/packages/protocol/test-sol/unit/common/FeeHandlerSeller.t.sol +++ b/packages/protocol/test-sol/unit/common/FeeHandlerSeller.t.sol @@ -11,7 +11,11 @@ import { FeeHandlerSeller } from "@celo-contracts/common/FeeHandlerSeller.sol"; import { MentoFeeHandlerSeller } from "@celo-contracts/common/MentoFeeHandlerSeller.sol"; import { UniswapFeeHandlerSeller } from "@celo-contracts/common/UniswapFeeHandlerSeller.sol"; +import "@celo-contracts/common/interfaces/IRegistry.sol"; + contract FeeHandlerSellerTest is Test, TestConstants { + event OracleAddressSet(address _token, address _oracle); + // Actors address RECEIVER_ADDRESS = actor("Arbitrary Receiver"); address NON_OWNER_ADDRESS = actor("Arbitrary Non-Owner"); @@ -21,15 +25,21 @@ contract FeeHandlerSellerTest is Test, TestConstants { FeeHandlerSeller mentoFeeHandlerSeller; FeeHandlerSeller uniswapFeeHandlerSeller; - // Contract addresses - address MOCK_CELO_TOKEN_ADDRESS; + address oracle; + address sortedOracles; // Helper data structures FeeHandlerSeller[] feeHandlerSellerInstances; function setUp() public { + deployCodeTo("Registry.sol", abi.encode(false), REGISTRY_ADDRESS); + IRegistry registry = IRegistry(REGISTRY_ADDRESS); + celoToken = new GoldTokenMock(); - MOCK_CELO_TOKEN_ADDRESS = address(celoToken); + oracle = actor("oracle"); + sortedOracles = actor("sortedOracles"); + + registry.setAddressFor("SortedOracles", sortedOracles); mentoFeeHandlerSeller = new MentoFeeHandlerSeller(true); uniswapFeeHandlerSeller = new UniswapFeeHandlerSeller(true); @@ -41,7 +51,7 @@ contract FeeHandlerSellerTest is Test, TestConstants { contract FeeHandlerSellerTest_Transfer is FeeHandlerSellerTest { uint256 constant ZERO_CELOTOKEN = 0; - uint256 constant ONE_CELOTOKEN = 1000000000000000000; + uint256 constant ONE_CELOTOKEN = 1e18; function test_FeeHandlerSeller_ShouldTransfer_WhenCalledByOwner() public { for (uint256 i = 0; i < feeHandlerSellerInstances.length; i++) { @@ -59,11 +69,7 @@ contract FeeHandlerSellerTest_Transfer is FeeHandlerSellerTest { ); vm.prank(feeHandlerSellerInstances[i].owner()); - feeHandlerSellerInstances[i].transfer( - MOCK_CELO_TOKEN_ADDRESS, - ONE_CELOTOKEN, - RECEIVER_ADDRESS - ); + feeHandlerSellerInstances[i].transfer(address(celoToken), ONE_CELOTOKEN, RECEIVER_ADDRESS); assertEq( celoToken.balanceOf(RECEIVER_ADDRESS), @@ -83,11 +89,7 @@ contract FeeHandlerSellerTest_Transfer is FeeHandlerSellerTest { vm.prank(NON_OWNER_ADDRESS); vm.expectRevert("Ownable: caller is not the owner"); - feeHandlerSellerInstances[i].transfer( - MOCK_CELO_TOKEN_ADDRESS, - ONE_CELOTOKEN, - RECEIVER_ADDRESS - ); + feeHandlerSellerInstances[i].transfer(address(celoToken), ONE_CELOTOKEN, RECEIVER_ADDRESS); } } } @@ -125,3 +127,27 @@ contract FeeHandlerSellerTest_SetMinimumReports is FeeHandlerSellerTest { } } } + +contract FeeHandlerSellerTest_setOracleAddress is FeeHandlerSellerTest { + function test_Reverts_WhenCalledByNonOwner() public { + vm.prank(NON_OWNER_ADDRESS); + vm.expectRevert("Ownable: caller is not the owner"); + uniswapFeeHandlerSeller.setOracleAddress(address(celoToken), oracle); + } + + function test_SetsCorrectly() public { + uniswapFeeHandlerSeller.setOracleAddress(address(celoToken), oracle); + assertEq(uniswapFeeHandlerSeller.getOracleAddress(address(celoToken)), oracle); + } + + function test_DefaultIsSortedOracles() public { + uniswapFeeHandlerSeller.initialize(REGISTRY_ADDRESS, new address[](0), new uint256[](0)); + assertEq(uniswapFeeHandlerSeller.getOracleAddress(address(celoToken)), sortedOracles); + } + + function test_Emits_OracleAddressSet() public { + vm.expectEmit(false, false, false, true); + emit OracleAddressSet(address(celoToken), oracle); + uniswapFeeHandlerSeller.setOracleAddress(address(celoToken), oracle); + } +}