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

operator fee fixes #263

Merged
merged 11 commits into from
May 30, 2024
13 changes: 13 additions & 0 deletions abi/Profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,19 @@
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "nodeOperatorFeesStorage",
"outputs": [
{
"internalType": "contract NodeOperatorFeesStorage",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "parametersStorage",
Expand Down
8 changes: 6 additions & 2 deletions contracts/v1/Profile.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {ParametersStorage} from "./storage/ParametersStorage.sol";
import {ProfileStorage} from "./storage/ProfileStorage.sol";
import {StakingStorage} from "./storage/StakingStorage.sol";
import {Staking} from "./Staking.sol";
import {NodeOperatorFeesStorage} from "../v2/storage/NodeOperatorFeesStorage.sol";
import {WhitelistStorage} from "./storage/WhitelistStorage.sol";
import {ContractStatus} from "./abstract/ContractStatus.sol";
import {Initializable} from "./interface/Initializable.sol";
Expand All @@ -33,7 +34,7 @@ contract Profile is Named, Versioned, ContractStatus, Initializable {
event AskUpdated(uint72 indexed identityId, bytes nodeId, uint96 ask);

string private constant _NAME = "Profile";
string private constant _VERSION = "1.1.1";
string private constant _VERSION = "1.1.2";

HashingProxy public hashingProxy;
Identity public identityContract;
Expand All @@ -43,6 +44,7 @@ contract Profile is Named, Versioned, ContractStatus, Initializable {
ParametersStorage public parametersStorage;
ProfileStorage public profileStorage;
WhitelistStorage public whitelistStorage;
NodeOperatorFeesStorage public nodeOperatorFeesStorage;

// solhint-disable-next-line no-empty-blocks
constructor(address hubAddress) ContractStatus(hubAddress) {}
Expand Down Expand Up @@ -76,6 +78,7 @@ contract Profile is Named, Versioned, ContractStatus, Initializable {
parametersStorage = ParametersStorage(hub.getContractAddress("ParametersStorage"));
profileStorage = ProfileStorage(hub.getContractAddress("ProfileStorage"));
whitelistStorage = WhitelistStorage(hub.getContractAddress("WhitelistStorage"));
nodeOperatorFeesStorage = NodeOperatorFeesStorage(hub.getContractAddress("NodeOperatorFeesStorage"));
}

function name() external pure virtual override returns (string memory) {
Expand All @@ -96,6 +99,7 @@ contract Profile is Named, Versioned, ContractStatus, Initializable {
) external onlyWhitelisted {
IdentityStorage ids = identityStorage;
ProfileStorage ps = profileStorage;
NodeOperatorFeesStorage nofs = nodeOperatorFeesStorage;
Identity id = identityContract;

if (ids.getIdentityId(msg.sender) != 0) {
Expand Down Expand Up @@ -136,7 +140,7 @@ contract Profile is Named, Versioned, ContractStatus, Initializable {
ps.createProfile(identityId, nodeId, address(sharesContract));
_setAvailableNodeAddresses(identityId);

stakingStorage.setOperatorFee(identityId, initialOperatorFee);
nofs.addOperatorFee(identityId, initialOperatorFee, uint248(block.timestamp));

emit ProfileCreated(identityId, nodeId, adminWallet, address(sharesContract), initialOperatorFee);
}
Expand Down
10 changes: 4 additions & 6 deletions contracts/v2/storage/NodeOperatorFeesStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

contract NodeOperatorFeesStorage is Named, Versioned, HubDependent {
string private constant _NAME = "NodeOperatorFeesStorage";
string private constant _VERSION = "2.0.0";
string private constant _VERSION = "2.0.2";

bool private _delayFreePeriodSet;
uint256 public delayFreePeriodEnd;
Expand All @@ -19,7 +19,6 @@
// identityId => OperatorFee[]
mapping(uint72 => NodeOperatorStructs.OperatorFee[]) public operatorFees;

// solhint-disable-next-line no-empty-blocks
constructor(address hubAddress, uint256 migrationPeriodEnd_) HubDependent(hubAddress) {
migrationPeriodEnd = migrationPeriodEnd_;
}
Expand All @@ -43,12 +42,11 @@
return _VERSION;
}

function migrateOldOperatorFees(
NodeOperatorStructs.OperatorFees[] memory legacyFees
) external onlyHubOwner timeLimited {
function migrateOldOperatorFees(NodeOperatorStructs.OperatorFees[] memory legacyFees) external timeLimited {
for (uint i; i < legacyFees.length; ) {
operatorFees[legacyFees[i].identityId] = legacyFees[i].fees;
require(operatorFees[legacyFees[i].identityId].length == 0);

Check warning on line 47 in contracts/v2/storage/NodeOperatorFeesStorage.sol

View workflow job for this annotation

GitHub Actions / lint

Provide an error message for require

operatorFees[legacyFees[i].identityId] = legacyFees[i].fees;
unchecked {
i++;
}
Expand Down
207 changes: 139 additions & 68 deletions deploy/028_deploy_node_operator_fees_storage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { BigNumberish } from 'ethers';
import { DeployFunction } from 'hardhat-deploy/types';
import { HardhatRuntimeEnvironment } from 'hardhat/types';

import { ShardingTableStructsV1 } from '../typechain/contracts/v2/ShardingTable.sol/ShardingTableV2';

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const isDeployed = hre.helpers.isDeployed('NodeOperatorFeesStorage');
const isMigration = hre.helpers.contractDeployments.contracts['NodeOperatorFeesStorage']?.migration || false;

if (isDeployed) {
if (isDeployed && !isMigration) {
return;
}

Expand All @@ -15,22 +15,12 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {

const { deployer } = await hre.getNamedAccounts();

let shardingTableABI;
let shardingTableAddress;
if (Object.keys(hre.helpers.contractDeployments.contracts).includes('OldShardingTable')) {
shardingTableABI = hre.helpers.getAbi('ShardingTable');
shardingTableAddress = hre.helpers.contractDeployments.contracts['OldShardingTable'].evmAddress;
delete hre.helpers.contractDeployments.contracts['OldShardingTable'];
console.log(`Found V1 ShardingTable address: ${shardingTableAddress}`);
} else {
shardingTableABI = hre.helpers.getAbi('ShardingTableV2');
shardingTableAddress = hre.helpers.contractDeployments.contracts['ShardingTable'].evmAddress;
console.log(`Found V2 ShardingTable address: ${shardingTableAddress}`);
}
const ShardingTable = await hre.ethers.getContractAt(shardingTableABI, shardingTableAddress, deployer);

const stakingStorageAddress = hre.helpers.contractDeployments.contracts['StakingStorage'].evmAddress;
const StakingStorage = await hre.ethers.getContractAt('StakingStorage', stakingStorageAddress, deployer);
const oldNodeOperatorFeesStorageAddress =
hre.helpers.contractDeployments.contracts['NodeOperatorFeesStorage'].evmAddress;
const OldNodeOperatorFeesStorage = await hre.ethers.getContractAt(
'NodeOperatorFeesStorage',
oldNodeOperatorFeesStorageAddress,
);

const nofcsAddress = hre.helpers.contractDeployments.contracts['NodeOperatorFeeChangesStorage']?.evmAddress;
let nofcs = null;
Expand All @@ -39,43 +29,133 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
nofcs = await hre.ethers.getContractAt(abi, nofcsAddress, deployer);
}

console.log(`Getting list of nodes in Sharding Table...`);
const nodes: ShardingTableStructsV1.NodeInfoStructOutput[] = await ShardingTable['getShardingTable()']();
const identityIds = nodes.map((node) => node.identityId);
const stakingStorageAddress = hre.helpers.contractDeployments.contracts['StakingStorage'].evmAddress;
const StakingStorage = await hre.ethers.getContractAt('StakingStorage', stakingStorageAddress, deployer);

console.log(`Starting migration of the old operator fees...`);
for (const identityId of identityIds) {
const storageLayout = {
astId: 1238,
contract: 'IdentityStorageFlattened.sol:IdentityStorage',
label: '_identityId',
offset: 20,
slot: 0,
type: 't_uint72',
};
const storageVariableType = {
t_uint72: {
encoding: 'inplace',
label: 'uint72',
numberOfBytes: 9,
},
};

console.log('Getting current next identityId from IdentityStorage...');
const storageSlot = await hre.ethers.provider.getStorageAt(
hre.helpers.contractDeployments.contracts['IdentityStorage'].evmAddress,
0,
);
const variableSlot = storageSlot.slice(
storageSlot.length -
2 *
(storageLayout.offset +
storageVariableType[storageLayout.type as keyof typeof storageVariableType].numberOfBytes),
storageSlot.length - storageLayout.offset * 2,
);
console.log(`Storage slot ${storageLayout.slot}: ${storageSlot}`);
console.log(`Variable slot: ${variableSlot}`);
const nextIdentityId = parseInt(variableSlot, 16);
console.log(`Current next identityId: ${nextIdentityId}`);

console.log(`Starting migration of the old operator fees... Latest identityId: ${nextIdentityId - 1}`);
for (let identityId = 1; identityId < nextIdentityId; identityId++) {
console.log(`--------------------------------------------------------`);
console.log(`IdentityId: ${identityId}`);

const operatorFees = [];
let operatorFees: { feePercentage: BigNumberish; effectiveDate: number }[] = [];

const activeOperatorFeePercentage = await StakingStorage.operatorFees(identityId);
const oldContractOperatorFees = await OldNodeOperatorFeesStorage.getOperatorFees(identityId);

console.log(`Active operatorFee in the StakingStorage: ${activeOperatorFeePercentage.toString()}%`);
console.log(`Old operatorFees in the old NodeOperatorFeesStorage: ${JSON.stringify(oldContractOperatorFees)}`);

if (!activeOperatorFeePercentage.eq(0)) {
operatorFees.push({
feePercentage: activeOperatorFeePercentage,
effectiveDate: timestampNow,
if (oldContractOperatorFees.length != 0) {
const fees = oldContractOperatorFees.map((x: BigNumberish[]) => {
return { feePercentage: x[0], effectiveDate: Number(x[1].toString()) };
});

if (hre.network.name.startsWith('gnosis')) {
operatorFees = operatorFees.concat(fees);
} else {
oldOperatorFees.push({
identityId,
fees,
});
continue;
}
}

let stakingStorageSource = false;
if (operatorFees.length == 0) {
const activeOperatorFeePercentage = await StakingStorage.operatorFees(identityId);

console.log(`Active operatorFee in the StakingStorage: ${activeOperatorFeePercentage.toString()}%`);

if (!activeOperatorFeePercentage.eq(0)) {
operatorFees.push({
feePercentage: activeOperatorFeePercentage,
effectiveDate: timestampNow,
});
stakingStorageSource = true;
}
}

if (nofcs !== null) {
const pendingOperatorFee = await nofcs.operatorFeeChangeRequests(identityId);

if (Number(pendingOperatorFee.timestamp.toString() == 0)) {
if (operatorFees.length > 0) {
oldOperatorFees.push({
identityId,
fees: operatorFees,
});
} else {
oldOperatorFees.push({
identityId,
fees: [{ feePercentage: 0, effectiveDate: timestampNow }],
});
}
continue;
}

console.log(`Pending operatorFee in the NodeOperatorFeeChangesStorage: ${pendingOperatorFee.newFee.toString()}%`);

if (!pendingOperatorFee.timestamp.eq(0)) {
if (pendingOperatorFee.timestamp < operatorFees[0].effectiveDate) {
operatorFees[0].effectiveDate = pendingOperatorFee.timestamp - 1;
}
const exists = operatorFees.some(
(obj) =>
Number(obj.feePercentage.toString()) === Number(pendingOperatorFee.newFee.toString()) &&
Number(obj.effectiveDate.toString()) === Number(pendingOperatorFee.timestamp.toString()),
);

operatorFees.push({
feePercentage: pendingOperatorFee.newFee,
effectiveDate: pendingOperatorFee.timestamp,
if (exists) {
console.log(`Pending operatorFee is already a part of the fees array from old NodeOperatorFeesStorage`);
oldOperatorFees.push({
identityId,
fees: operatorFees,
});
continue;
}

if (
(stakingStorageSource &&
Number(pendingOperatorFee.timestamp.toString()) < Number(operatorFees[0].effectiveDate.toString())) ||
(operatorFees.length == 1 && Number(operatorFees[0].effectiveDate.toString()) == 1716291685)
) {
operatorFees[0].effectiveDate = Number(pendingOperatorFee.timestamp.toString()) - 1;
}

operatorFees.push({
feePercentage: pendingOperatorFee.newFee,
effectiveDate: Number(pendingOperatorFee.timestamp.toString()),
});

operatorFees.sort((a, b) => a.effectiveDate - b.effectiveDate);
}

console.log(`--------------------------------------------------------`);
Expand All @@ -85,6 +165,11 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
identityId,
fees: operatorFees,
});
} else {
oldOperatorFees.push({
identityId,
fees: [{ feePercentage: 0, effectiveDate: timestampNow }],
});
}
}

Expand All @@ -98,38 +183,24 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
});

const chunkSize = 10;
const encodedDataArray: string[] = oldOperatorFees.reduce<string[]>((acc, _, currentIndex, array) => {
if (currentIndex % chunkSize === 0) {
// Encode and push the function data for a slice of the array
acc.push(
NodeOperatorFeesStorage.interface.encodeFunctionData('migrateOldOperatorFees', [
array.slice(currentIndex, currentIndex + chunkSize),
]),
);
}
return acc;
}, []);

if (hre.network.config.environment === 'development') {
const { deployer } = await hre.getNamedAccounts();

const hubControllerAddress = hre.helpers.contractDeployments.contracts['HubController'].evmAddress;
const HubController = await hre.ethers.getContractAt('HubController', hubControllerAddress, deployer);

for (let i = 0; i < encodedDataArray.length; i++) {
const migrateOldOperatorFeesTx = await HubController.forwardCall(
NodeOperatorFeesStorage.address,
encodedDataArray[i],
);
await migrateOldOperatorFeesTx.wait();
}
} else {
for (let i = 0; i < encodedDataArray.length; i++) {
hre.helpers.setParametersEncodedData.push(['NodeOperatorFeesStorage', [encodedDataArray[i]]]);
}
const totalChunks = Math.ceil(oldOperatorFees.length / chunkSize);

console.log(`Starting migration of operator fees for ${oldOperatorFees.length} nodes...`);
for (let i = 0; i < oldOperatorFees.length; i += chunkSize) {
const chunk = oldOperatorFees.slice(i, i + chunkSize);
const chunkNumber = Math.floor(i / chunkSize) + 1;
const percentageDone = ((chunkNumber / totalChunks) * 100).toFixed(2);
console.log(
`Processing chunk ${chunkNumber} out of ${totalChunks} (starting at index ${i}):`,
JSON.stringify(chunk),
);
console.log(`Percentage done: ${percentageDone}%`);

const tx = await NodeOperatorFeesStorage.migrateOldOperatorFees(chunk);
await tx.wait();
}
};

export default func;
func.tags = ['NodeOperatorFeesStorage', 'v2'];
func.dependencies = ['HubV2', 'StakingStorage', 'ShardingTableV2'];
func.dependencies = ['HubV2', 'ContentAssetStorageV2', 'StakingStorage', 'ShardingTableV2'];
Loading
Loading