From 038a63fc5eaa1899a1708c0c9066f8bee4e75b9e Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Fri, 2 Feb 2024 12:18:43 +0200 Subject: [PATCH 1/8] wip --- contracts/helpers/EnumerableAddressSet.sol | 143 +++++++++++++++++++++ contracts/masset/OldRewardManager.sol | 0 contracts/masset/RewardManager.sol | 43 ++++--- scripts/src/rewardManagerBenchmark.ts | 65 ++++++++++ 4 files changed, 234 insertions(+), 17 deletions(-) create mode 100644 contracts/helpers/EnumerableAddressSet.sol create mode 100644 contracts/masset/OldRewardManager.sol create mode 100644 scripts/src/rewardManagerBenchmark.ts diff --git a/contracts/helpers/EnumerableAddressSet.sol b/contracts/helpers/EnumerableAddressSet.sol new file mode 100644 index 0000000..7312af3 --- /dev/null +++ b/contracts/helpers/EnumerableAddressSet.sol @@ -0,0 +1,143 @@ +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) { + address[] memory output = new address[](set.values.length); + for (uint256 i; i < set.values.length; i++) { + output[i] = set.values[i]; + } + return output; + } + + /** + * @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/masset/OldRewardManager.sol b/contracts/masset/OldRewardManager.sol new file mode 100644 index 0000000..e69de29 diff --git a/contracts/masset/RewardManager.sol b/contracts/masset/RewardManager.sol index f7f1d89..4039488 100644 --- a/contracts/masset/RewardManager.sol +++ b/contracts/masset/RewardManager.sol @@ -7,11 +7,12 @@ import { IERC20 } from "../openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Address } from "../openzeppelin/contracts/utils/Address.sol"; import { IRewardManager } from "./IRewardManager.sol"; import { IMasset } from "./IMasset.sol"; -import { MappingAddressToUint256 } from "../helpers/MappingAddressToUint256.sol"; +import { EnumerableAddressSet } from "../helpers/EnumerableAddressSet.sol"; /// @title A contract to manage rewards and penalties /// @author Derek Matter contract RewardManager is IRewardManager, Ownable { + using EnumerableAddressSet for EnumerableAddressSet.AddressSet; // enumerable map of addresses using SafeMath for uint256; uint256 constant ONE = 1000000000000000000; @@ -21,7 +22,8 @@ contract RewardManager is IRewardManager, Ownable { IMasset private masset; uint256 private factor = 0; - MappingAddressToUint256 private targetWeights = new MappingAddressToUint256(); + EnumerableAddressSet.AddressSet private tokensSet; + mapping(address => 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/scripts/src/rewardManagerBenchmark.ts b/scripts/src/rewardManagerBenchmark.ts new file mode 100644 index 0000000..6a5794e --- /dev/null +++ b/scripts/src/rewardManagerBenchmark.ts @@ -0,0 +1,65 @@ +import state from '../../migrations/state'; +import { BasketManagerInstance, ERC20Instance, MassetInstance, MassetProxyInstance, TokenInstance } from 'types/generated'; +import { knownBridges, knownFactors, knownTokens, knownTokensAddressToName } from './knownTokens'; +import { ZERO_ADDRESS } from '@utils/constants'; +import BN from "bn.js"; + +const ONE = new BN('1000000000000000000'); + +export default async function upgradeRewardManager(truffle): Promise { + + const artifacts = truffle.artifacts; + const provider = truffle.web3.currentProvider; + const network = truffle.artifacts.options.network; + state.setNetwork(network); + + console.log(provider.getAddress(0), provider.getAddress(1)); + + const Token = artifacts.require("Token"); + + const Masset = artifacts.require("Masset"); + const masset = await state.getDeployed(Masset, 'MassetProxy'); + const basketManagerAddress = await (masset as MassetInstance).getBasketManager(); + const BasketManager = artifacts.require("BasketManager"); + const basketManager = await BasketManager.at(basketManagerAddress); + + let bassets = await (basketManager as BasketManagerInstance).getBassets(); + bassets = bassets.map(s => s.toLowerCase()); + + const tokenNames = knownTokensAddressToName[network]; + + for(const token of bassets) { + console.log('token: ', tokenNames[token]); + } + + //await (basketManager as BasketManagerInstance).removeBasset(knownTokens[network]['TST5']); + + // const token = await Token.at('0xa9262CC3fB54Ea55B1B0af00EfCa9416B8d59570'); + // await (token as TokenInstance).mint('0x94e907f6B903A393E14FE549113137CA6483b5ef', '100000000000000000000000000'); + // await (token as TokenInstance).mint('0xC1aEAafa4a9Bf0A74464D496D220Bf89Ce9BF901', '100000000000000000000000000'); + + // const tokens = ['TST6']; + + // for(const symbol of tokens) { + // const token = await Token.new(); + // await (token as TokenInstance).initialize(symbol, symbol, 18); + // await (token as TokenInstance).mint('0x94e907f6B903A393E14FE549113137CA6483b5ef', '100000000000000000000000000'); + // await (token as TokenInstance).mint('0xC1aEAafa4a9Bf0A74464D496D220Bf89Ce9BF901', '100000000000000000000000000'); + // console.log('created ', symbol, ' adress: ', token.address); + // } + + // for(const basset of bassets) { + // console.log('removing ', basset); + // await (basketManager as BasketManagerInstance).removeBasset(basset); + // } + + + // for(const symbol of Object.keys(knownTokens.rskTestnet)) { + // const basset = knownTokens.rskTestnet[symbol]; + // if(bassets.find(s => s == basset)) continue; + // console.log('adding ', symbol, ' address ', basset); + // const bridge = knownBridges.rskTestnet[symbol]; + // const factor = knownFactors.rskTestnet[symbol]; + // await (basketManager as BasketManagerInstance).addBasset(basset, factor, bridge); + // } +} From 7c85cf48d5c952353fad106a32e95b7ef6138280 Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Mon, 5 Feb 2024 11:13:43 +0200 Subject: [PATCH 2/8] optimization --- contracts/helpers/EnumerableAddressSet.sol | 6 +----- contracts/masset/RewardManager.sol | 11 ++++++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/contracts/helpers/EnumerableAddressSet.sol b/contracts/helpers/EnumerableAddressSet.sol index 7312af3..7ef2792 100644 --- a/contracts/helpers/EnumerableAddressSet.sol +++ b/contracts/helpers/EnumerableAddressSet.sol @@ -85,11 +85,7 @@ library EnumerableAddressSet { * {get} instead in these cases. */ function enumerate(AddressSet storage set) internal view returns (address[] memory) { - address[] memory output = new address[](set.values.length); - for (uint256 i; i < set.values.length; i++) { - output[i] = set.values[i]; - } - return output; + return set.values; } /** diff --git a/contracts/masset/RewardManager.sol b/contracts/masset/RewardManager.sol index 4039488..d37e83d 100644 --- a/contracts/masset/RewardManager.sol +++ b/contracts/masset/RewardManager.sol @@ -43,6 +43,7 @@ contract RewardManager is IRewardManager, Ownable { /** External methods **/ +/* /// @notice Constructor Creates a new RewardManager with a given Masset and initializes it with the current RewardManager params /// @param _massetAddress current masset /// @param _copyCurrentParams whether to copy the params from the existing RM @@ -58,6 +59,10 @@ contract RewardManager is IRewardManager, Ownable { setGlobalMaxPenaltyPerc(previous.getGlobalMaxPenaltyPerc()); setTargetWeights(previous.getTokens(), previous.getTargetWeights()); } + }*/ + + function setMasset(address _massetAddress) public onlyOwner() { + masset = IMasset(_massetAddress); } /** Getters **/ @@ -230,7 +235,7 @@ contract RewardManager is IRewardManager, Ownable { for(uint i=0; i Date: Mon, 5 Feb 2024 11:16:07 +0200 Subject: [PATCH 3/8] removed redundant script --- scripts/src/rewardManagerBenchmark.ts | 65 --------------------------- 1 file changed, 65 deletions(-) delete mode 100644 scripts/src/rewardManagerBenchmark.ts diff --git a/scripts/src/rewardManagerBenchmark.ts b/scripts/src/rewardManagerBenchmark.ts deleted file mode 100644 index 6a5794e..0000000 --- a/scripts/src/rewardManagerBenchmark.ts +++ /dev/null @@ -1,65 +0,0 @@ -import state from '../../migrations/state'; -import { BasketManagerInstance, ERC20Instance, MassetInstance, MassetProxyInstance, TokenInstance } from 'types/generated'; -import { knownBridges, knownFactors, knownTokens, knownTokensAddressToName } from './knownTokens'; -import { ZERO_ADDRESS } from '@utils/constants'; -import BN from "bn.js"; - -const ONE = new BN('1000000000000000000'); - -export default async function upgradeRewardManager(truffle): Promise { - - const artifacts = truffle.artifacts; - const provider = truffle.web3.currentProvider; - const network = truffle.artifacts.options.network; - state.setNetwork(network); - - console.log(provider.getAddress(0), provider.getAddress(1)); - - const Token = artifacts.require("Token"); - - const Masset = artifacts.require("Masset"); - const masset = await state.getDeployed(Masset, 'MassetProxy'); - const basketManagerAddress = await (masset as MassetInstance).getBasketManager(); - const BasketManager = artifacts.require("BasketManager"); - const basketManager = await BasketManager.at(basketManagerAddress); - - let bassets = await (basketManager as BasketManagerInstance).getBassets(); - bassets = bassets.map(s => s.toLowerCase()); - - const tokenNames = knownTokensAddressToName[network]; - - for(const token of bassets) { - console.log('token: ', tokenNames[token]); - } - - //await (basketManager as BasketManagerInstance).removeBasset(knownTokens[network]['TST5']); - - // const token = await Token.at('0xa9262CC3fB54Ea55B1B0af00EfCa9416B8d59570'); - // await (token as TokenInstance).mint('0x94e907f6B903A393E14FE549113137CA6483b5ef', '100000000000000000000000000'); - // await (token as TokenInstance).mint('0xC1aEAafa4a9Bf0A74464D496D220Bf89Ce9BF901', '100000000000000000000000000'); - - // const tokens = ['TST6']; - - // for(const symbol of tokens) { - // const token = await Token.new(); - // await (token as TokenInstance).initialize(symbol, symbol, 18); - // await (token as TokenInstance).mint('0x94e907f6B903A393E14FE549113137CA6483b5ef', '100000000000000000000000000'); - // await (token as TokenInstance).mint('0xC1aEAafa4a9Bf0A74464D496D220Bf89Ce9BF901', '100000000000000000000000000'); - // console.log('created ', symbol, ' adress: ', token.address); - // } - - // for(const basset of bassets) { - // console.log('removing ', basset); - // await (basketManager as BasketManagerInstance).removeBasset(basset); - // } - - - // for(const symbol of Object.keys(knownTokens.rskTestnet)) { - // const basset = knownTokens.rskTestnet[symbol]; - // if(bassets.find(s => s == basset)) continue; - // console.log('adding ', symbol, ' address ', basset); - // const bridge = knownBridges.rskTestnet[symbol]; - // const factor = knownFactors.rskTestnet[symbol]; - // await (basketManager as BasketManagerInstance).addBasset(basset, factor, bridge); - // } -} From 5854bd1550fb2794f0035e687bc034b1d2a16e1d Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Mon, 5 Feb 2024 11:17:11 +0200 Subject: [PATCH 4/8] removed old code --- contracts/masset/OldRewardManager.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 contracts/masset/OldRewardManager.sol diff --git a/contracts/masset/OldRewardManager.sol b/contracts/masset/OldRewardManager.sol deleted file mode 100644 index e69de29..0000000 From cf840e55ee957524ccbf234f696315ea1af736da Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Mon, 5 Feb 2024 11:17:22 +0200 Subject: [PATCH 5/8] removed old code --- contracts/helpers/MappingAddressToUint256.sol | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 contracts/helpers/MappingAddressToUint256.sol 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 Date: Mon, 5 Feb 2024 11:18:56 +0200 Subject: [PATCH 6/8] removed old code --- test/TestMappingAddressToUint256.spec.ts | 87 ------------------------ 1 file changed, 87 deletions(-) delete mode 100644 test/TestMappingAddressToUint256.spec.ts 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); - } -} From 31e6eaaf97ac6ddc9f134ab633527d2c1dd546d9 Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Mon, 5 Feb 2024 11:23:41 +0200 Subject: [PATCH 7/8] fixed constructor --- contracts/masset/RewardManager.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/masset/RewardManager.sol b/contracts/masset/RewardManager.sol index d37e83d..667db02 100644 --- a/contracts/masset/RewardManager.sol +++ b/contracts/masset/RewardManager.sol @@ -43,7 +43,6 @@ contract RewardManager is IRewardManager, Ownable { /** External methods **/ -/* /// @notice Constructor Creates a new RewardManager with a given Masset and initializes it with the current RewardManager params /// @param _massetAddress current masset /// @param _copyCurrentParams whether to copy the params from the existing RM @@ -59,7 +58,7 @@ contract RewardManager is IRewardManager, Ownable { setGlobalMaxPenaltyPerc(previous.getGlobalMaxPenaltyPerc()); setTargetWeights(previous.getTokens(), previous.getTargetWeights()); } - }*/ + } function setMasset(address _massetAddress) public onlyOwner() { masset = IMasset(_massetAddress); From 837b943045842a7edfc9c0009cd360190ae8e59d Mon Sep 17 00:00:00 2001 From: Derek Matter Date: Mon, 5 Feb 2024 11:25:50 +0200 Subject: [PATCH 8/8] removed setter left behind from testing --- contracts/masset/RewardManager.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/masset/RewardManager.sol b/contracts/masset/RewardManager.sol index 667db02..52cb937 100644 --- a/contracts/masset/RewardManager.sol +++ b/contracts/masset/RewardManager.sol @@ -60,10 +60,6 @@ contract RewardManager is IRewardManager, Ownable { } } - function setMasset(address _massetAddress) public onlyOwner() { - masset = IMasset(_massetAddress); - } - /** Getters **/ function getVersion() public pure returns (string memory) {