diff --git a/contracts/helpers/EnumerableAddressSet.sol b/contracts/helpers/EnumerableAddressSet.sol new file mode 100644 index 0000000..7ef2792 --- /dev/null +++ b/contracts/helpers/EnumerableAddressSet.sol @@ -0,0 +1,139 @@ +pragma solidity ^0.5.0; + +/** + * @dev Based on Library for managing + * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive + * types. + * + * Sets have the following properties: + * + * - Elements are added, removed, and checked for existence in constant time + * (O(1)). + * - Elements are enumerated in O(n). No guarantees are made on the ordering. + * + * As of v2.5.0, only `address` sets are supported. + * + * Include with `using EnumerableSet for EnumerableSet.AddressSet;`. + * + * _Available since v2.5.0._ + */ +library EnumerableAddressSet { + struct AddressSet { + // Position of the value in the `values` array, plus 1 because index 0 + // means a value is not in the set. + mapping(address => uint256) index; + address[] values; + } + + /** + * @dev Add a value to a set. O(1). + * Returns false if the value was already in the set. + */ + function add(AddressSet storage set, address value) internal returns (bool) { + if (!contains(set, value)) { + set.index[value] = set.values.push(value); + return true; + } else { + return false; + } + } + + /** + * @dev Removes a value from a set. O(1). + * Returns false if the value was not present in the set. + */ + function remove(AddressSet storage set, address value) internal returns (bool) { + if (contains(set, value)) { + uint256 toDeleteIndex = set.index[value] - 1; + uint256 lastIndex = set.values.length - 1; + + // If the element we're deleting is the last one, we can just remove it without doing a swap + if (lastIndex != toDeleteIndex) { + address lastValue = set.values[lastIndex]; + + // Move the last value to the index where the deleted value is + set.values[toDeleteIndex] = lastValue; + // Update the index for the moved value + set.index[lastValue] = toDeleteIndex + 1; // All indexes are 1-based + } + + // Delete the index entry for the deleted value + delete set.index[value]; + + // Delete the old entry for the moved value + set.values.pop(); + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(AddressSet storage set, address value) internal view returns (bool) { + return set.index[value] != 0; + } + + /** + * @dev Returns an array with all values in the set. O(N). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + + * WARNING: This function may run out of gas on large sets: use {length} and + * {get} instead in these cases. + */ + function enumerate(AddressSet storage set) internal view returns (address[] memory) { + return set.values; + } + + /** + * @dev Returns a chunk of array as recommended in enumerate() to avoid running of gas. + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + + * WARNING: This function may run out of gas on large sets: use {length} and + * {get} instead in these cases. + + * @param start start index of chunk + * @param count num of element to return; if count == 0 then returns all the elements from the @param start + */ + function enumerateChunk( + AddressSet storage set, + uint256 start, + uint256 count + ) internal view returns (address[] memory output) { + uint256 end = start + count; + require(end >= start, "addition overflow"); + end = (set.values.length < end || count == 0) ? set.values.length : end; + if (end == 0 || start >= end) { + return output; + } + + output = new address[](end - start); + for (uint256 i; i < end - start; i++) { + output[i] = set.values[i + start]; + } + return output; + } + + /** + * @dev Returns the number of elements on the set. O(1). + */ + function length(AddressSet storage set) internal view returns (uint256) { + return set.values.length; + } + + /** @dev Returns the element stored at position `index` in the set. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function get(AddressSet storage set, uint256 index) internal view returns (address) { + return set.values[index]; + } +} diff --git a/contracts/helpers/MappingAddressToUint256.sol b/contracts/helpers/MappingAddressToUint256.sol deleted file mode 100644 index 5486432..0000000 --- a/contracts/helpers/MappingAddressToUint256.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.5.17; - -import { Ownable } from "../openzeppelin/contracts/ownership/Ownable.sol"; - -contract MappingAddressToUint256 is Ownable { - - mapping(address => uint256) private values; - mapping(address => bool) private flags; - address[] private keys; - - constructor() public { - } - - /** getters */ - - function get(address _address) public view returns(uint256) { - return values[_address]; - } - - function getKeys() public view returns(address[] memory) { - return keys; - } - - function getValues() public view returns(uint256[] memory) { - uint256[] memory r = new uint256[](keys.length); - // CAVEAT: if the number of keys exceeds a certain limit, this function - // will no longer be able to run within the limits of a single transaction - for(uint i=0; i uint256) private targetWeights; uint256 globalMaxPenaltyPerc = 0; uint256 globalMaxRewardPerc = 0; @@ -73,19 +75,24 @@ contract RewardManager is IRewardManager, Ownable { /// @notice Get the list of tokens /// @return tokens the tokens function getTokens() public view returns (address[] memory) { - return targetWeights.getKeys(); + return tokensSet.enumerate(); } /// @notice Get the target weight for a token /// @return targetWeight the target weight function getTargetWeight(address _tokenAddress) public view returns (uint256) { - return targetWeights.get(_tokenAddress); + return targetWeights[_tokenAddress]; } /// @notice Get the target weights for all tokens /// @return targetWeights the target weights function getTargetWeights() public view returns (uint256[] memory) { - return targetWeights.getValues(); + address[] memory keys = getTokens(); + uint256[] memory r = new uint256[](keys.length); + for(uint i=0; i 0, "no target weights"); + require(getTokens().length > 0, "no target weights"); if(!isDepositDeservesReward(_bassetAddress, _sum, _bridgeMode)) { return 0; @@ -340,7 +349,7 @@ contract RewardManager is IRewardManager, Ownable { require(_sum < 1000000000000 * ONE, "_sum is too big"); // prevent overflow when casting to signed // uninitialized! avoid division by zero - require(targetWeights.getKeys().length > 0, "no target weights"); + require(getTokens().length > 0, "no target weights"); (uint256 dsqrBefore, uint256 dsqrAfter) = getAverageDsqrs(_bassetAddress, 0 - int256(_sum)); diff --git a/test/TestMappingAddressToUint256.spec.ts b/test/TestMappingAddressToUint256.spec.ts deleted file mode 100644 index 1f983f1..0000000 --- a/test/TestMappingAddressToUint256.spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable @typescript-eslint/no-use-before-define */ -import { expectRevert } from "@openzeppelin/test-helpers"; -import { StandardAccounts } from "@utils/standardAccounts"; -import envSetup from "@utils/env_setup"; -import { MappingAddressToUint256Instance } from "types/generated"; - -const { expect } = envSetup.configure(); - -const ADDRESS_1 = '0x0000000000000000000000000000000000000001'; -const ADDRESS_2 = '0x0000000000000000000000000000000000000002'; -const ADDRESS_3 = '0x0000000000000000000000000000000000000003'; - -const MappingAddressToUint256 = artifacts.require("MappingAddressToUint256"); - -contract("BasketManager", async (accounts) => { - const sa = new StandardAccounts(accounts); - - let mapping: MappingAddressToUint256Instance; - - before("before all", async () => { - mapping = await MappingAddressToUint256.new(); - }); - - describe("constructor", async () => { - it("owner should be default address", async () => { - expect(await mapping.owner()).to.eq(sa.default); - }); - }); - - describe("set", async () => { - - const expectedKeys = [ ADDRESS_1, ADDRESS_2, ADDRESS_3 ]; - const expectedValues = [ 10, 20, 30 ]; - - context("positive", () => { - - before(async () => { - for(let i = 0; i < expectedKeys.length; i++) { - await mapping.set(expectedKeys[i], expectedValues[i]); - } - }); - - it("set values keeps the values", async () => { - - for(let i = 0; i < expectedKeys.length; i++) { - const v = (await mapping.get(expectedKeys[i])).toNumber(); - expect(v).to.eq(expectedValues[i]); - const f = await mapping.exists(expectedKeys[i]); - expect(f).to.eq(true); - } - }); - - it("adds keys and values in array", async () => { - - const keys = await mapping.getKeys(); - const values = (await mapping.getValues()).map(v => v.toNumber()); - - testArrayNoOrder(keys, expectedKeys); - testArrayNoOrder(values, expectedValues); - }); - - it("zero value still exists", async () => { - - await mapping.set(ADDRESS_2, 0); - - const keys = await mapping.getKeys(); - expect(!!keys.find(k => ADDRESS_2)).to.eq(true); - const f = await mapping.exists(ADDRESS_2); - expect(f).to.eq(true); - }); - }); - - it("set should only work for owner", async () => { - - await expectRevert.unspecified(mapping.set(ADDRESS_1, 0, { from: sa.dummy1 })); - }); - }); -}); - -function testArrayNoOrder(actual: any[], expected: any[]) { - expect(actual.length).to.eq(expected.length); - for(let i = 0; i < expected.length; i++) { - const f = !!actual.find(k => expected[i]); - expect(f).to.eq(true); - } -}