Skip to content

Commit

Permalink
Add oracle setter to FeeHandler (#11242)
Browse files Browse the repository at this point in the history
  • Loading branch information
martinvol authored Oct 17, 2024
1 parent a5a33dc commit 89591e1
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 43 deletions.
3 changes: 2 additions & 1 deletion packages/protocol/contracts/common/FeeHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down
22 changes: 20 additions & 2 deletions packages/protocol/contracts/common/FeeHandlerSeller.sol
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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());
}
}
3 changes: 2 additions & 1 deletion packages/protocol/contracts/common/MentoFeeHandlerSeller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
}
19 changes: 12 additions & 7 deletions packages/protocol/contracts/common/UniswapFeeHandlerSeller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
22 changes: 11 additions & 11 deletions packages/protocol/lib/compatibility/utils.ts
Original file line number Diff line number Diff line change
@@ -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';



Expand All @@ -16,7 +16,7 @@ import { readJsonSync } from 'fs-extra'
export class ASTBackwardReport {

static create = (
oldArtifactsFolder: string,
oldArtifactsFolders: string[],
newArtifactsFolders: string[],
oldArtifacts: BuildArtifacts[],
newArtifacts: BuildArtifacts[],
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"EpochManager": ["0x000000000000000000000000000000000000ce10", 86400],
"EpochManagerEnabler": ["0x000000000000000000000000000000000000ce10"],
"ScoreManager": [],
"FeeCurrencyDirectory": []
"FeeCurrencyDirectory": [],
"UniswapFeeHandlerSeller": ["0x000000000000000000000000000000000000ce10", [], []]
}
14 changes: 14 additions & 0 deletions packages/protocol/scripts/bash/contract-exclusion-regex.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
2 changes: 1 addition & 1 deletion packages/protocol/scripts/bash/release-on-devchain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion packages/protocol/scripts/check-backward.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -79,7 +80,7 @@ const newArtifacts08 = instantiateArtifacts(newArtifactsFolder08)

try {
const backward = ASTBackwardReport.create(
oldArtifactsFolder,
oldArtifactsFolders,
newArtifactsFolders,
[oldArtifacts, oldArtifacts08],
[newArtifacts, newArtifacts08],
Expand Down
3 changes: 0 additions & 3 deletions packages/protocol/test-sol/unit/common/FeeHandler.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
54 changes: 40 additions & 14 deletions packages/protocol/test-sol/unit/common/FeeHandlerSeller.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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);
Expand All @@ -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++) {
Expand All @@ -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),
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -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);
}
}

0 comments on commit 89591e1

Please sign in to comment.