Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
541 changes: 0 additions & 541 deletions .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs

This file was deleted.

9 changes: 0 additions & 9 deletions .yarn/plugins/@yarnpkg/plugin-typescript.cjs

This file was deleted.

550 changes: 0 additions & 550 deletions .yarn/plugins/@yarnpkg/plugin-version.cjs

This file was deleted.

874 changes: 0 additions & 874 deletions .yarn/releases/yarn-3.6.4.cjs

This file was deleted.

942 changes: 942 additions & 0 deletions .yarn/releases/yarn-4.10.3.cjs

Large diffs are not rendered by default.

18 changes: 5 additions & 13 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
changesetBaseRefs:
- main
- origin/main
- main
- origin/main

enableImmutableInstalls: false

enableProgressBars: false

nodeLinker: node-modules

npmAuthToken: '${NPM_TOKEN:-}'
npmAuthToken: "${NPM_TOKEN:-}"

npmPublishRegistry: 'https://registry.npmjs.org'
npmPublishRegistry: "https://registry.npmjs.org"

plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: '@yarnpkg/plugin-typescript'
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
spec: '@yarnpkg/plugin-version'
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: '@yarnpkg/plugin-interactive-tools'

yarnPath: .yarn/releases/yarn-3.6.4.cjs
yarnPath: .yarn/releases/yarn-4.10.3.cjs
82 changes: 78 additions & 4 deletions contracts/Staking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ contract Staking is IStaking, Initializable, OwnableUpgradeable, SQParameter {
// Staking runner lengths
mapping(address => uint256) public stakingIndexerLengths;

// Instant delegation quota per era (per wallet)
uint256 public instantDelegationQuota;

// Era window percentage for instant delegation (in perMill, e.g., 700,000 = 70%)
uint256 public instantEraWindowPercent;

// Instant quota usage tracking

// Track instant quota used per delegator: delegator => QuotaUsage
mapping(address => InstantQuotaUsage) public instantQuotaUsed;

// -- Events --

/**
Expand Down Expand Up @@ -350,18 +361,20 @@ contract Staking is IStaking, Initializable, OwnableUpgradeable, SQParameter {
emit DelegationAdded2(_source, _runner, _amount, instant);
}

function delegateToIndexer(
/**
* @dev Transfer tokens from source to this contract (for instant delegation)
* @param _source The source address
* @param _amount The amount to transfer
*/
function transferDelegationTokens(
address _source,
address _runner,
uint256 _amount
) external onlyStakingManager {
IERC20(settings.getContractAddress(SQContracts.SQToken)).safeTransferFrom(
_source,
address(this),
_amount
);

this.addDelegation(_source, _runner, _amount, false);
}

function removeDelegation(address _source, address _runner, uint256 _amount) external {
Expand Down Expand Up @@ -492,11 +505,72 @@ contract Staking is IStaking, Initializable, OwnableUpgradeable, SQParameter {
this.startUnbond(_runner, _runner, _amount, UnbondType.Commission);
}

/**
* @notice Update instant quota used for a delegator in a specific era
* @param delegator The delegator address
* @param era The era number
* @param amount The amount to add to used quota
*/
function updateInstantQuotaUsed(
address delegator,
uint256 era,
uint256 amount
) external onlyStakingManager {
InstantQuotaUsage storage usage = instantQuotaUsed[delegator];

// If era changed, reset the quota usage
if (usage.era != era) {
usage.era = era;
usage.amount = amount;
} else {
// Same era, accumulate
usage.amount += amount;
}
}

/**
* @notice Set instant delegation parameters
* @param _perEraQuota The quota per era per wallet
* @param _windowPercent The era window percentage (in perMill)
*/
function setInstantDelegationParams(
uint256 _perEraQuota,
uint256 _windowPercent
) external onlyOwner {
require(_windowPercent <= PER_MILL, 'S015');
instantDelegationQuota = _perEraQuota;
instantEraWindowPercent = _windowPercent;

emit Parameter('instantDelegationQuota', abi.encode(_perEraQuota));
emit Parameter('instantEraWindowPercent', abi.encode(_windowPercent));
}

// -- Views --

function isEmptyDelegation(address _source, address _runner) external view returns (bool) {
return
delegation[_source][_runner].valueAt == 0 &&
delegation[_source][_runner].valueAfter == 0;
}

/**
* @notice Get remaining instant quota for a delegator in a specific era
* @param delegator The delegator address
* @param era The era number
* @return The remaining quota amount
*/
function getInstantQuotaRemaining(
address delegator,
uint256 era
) external view returns (uint256) {
InstantQuotaUsage memory usage = instantQuotaUsed[delegator];

// If different era or not yet used, full quota available
if (usage.era != era) {
return instantDelegationQuota;
}

// Same era, return remaining
return instantDelegationQuota > usage.amount ? instantDelegationQuota - usage.amount : 0;
}
}
108 changes: 105 additions & 3 deletions contracts/StakingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import './interfaces/IIndexerRegistry.sol';
import './interfaces/IStakingManager.sol';
import './utils/MathUtil.sol';
import './utils/StakingUtil.sol';
import './Constants.sol';
import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';

/**
* Split from Staking, to keep contract size under control
*/
contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
using MathUtil for uint256;

ISettings public settings;

/**
Expand Down Expand Up @@ -50,18 +53,59 @@ contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
} else {
require(msg.sender == _runner, 'G002');
}
staking.delegateToIndexer(_runner, _runner, _amount);
staking.transferDelegationTokens(_runner, _amount);
staking.addDelegation(_runner, _runner, _amount, false);
}

/**
* @dev Delegator stake to Indexer, Indexer cannot call this.
* Supports instant delegation with quota-based limits and era window restrictions.
*/
function delegate(address _runner, uint256 _amount) external {
require(msg.sender != _runner, 'G004');
Staking staking = Staking(settings.getContractAddress(SQContracts.Staking));
// delegation limit should not exceed

// Check delegation limitation
staking.checkDelegateLimitation(_runner, _amount);
staking.delegateToIndexer(msg.sender, _runner, _amount);

// Transfer tokens first
staking.transferDelegationTokens(msg.sender, _amount);

// Check era progress (70% window by default)
uint256 eraProgress = _calculateEraProgress();
uint256 windowPercent = staking.instantEraWindowPercent();
bool inInstantWindow = windowPercent > 0 && eraProgress <= windowPercent;

if (!inInstantWindow) {
// After window: all delegation is pending
staking.addDelegation(msg.sender, _runner, _amount, false);
return;
}

Comment thread
ianhe8x marked this conversation as resolved.
// Within window: check quota
uint256 remainingQuota = _getRemainingQuota(msg.sender);

if (_amount <= remainingQuota) {
// Case A: Fully instant
staking.addDelegation(msg.sender, _runner, _amount, true);
_applyInstantDelegation(msg.sender, _runner);
_consumeInstantQuota(msg.sender, _amount);
} else if (remainingQuota > 0) {
// Case B: Split - instant + pending
uint256 instantAmount = remainingQuota;
uint256 pendingAmount = _amount - remainingQuota;

// Instant portion
staking.addDelegation(msg.sender, _runner, instantAmount, true);
_applyInstantDelegation(msg.sender, _runner);
_consumeInstantQuota(msg.sender, instantAmount);

// Pending portion
staking.addDelegation(msg.sender, _runner, pendingAmount, false);
} else {
// Case C: Quota exhausted - all pending
staking.addDelegation(msg.sender, _runner, _amount, false);
}
}

/**
Expand Down Expand Up @@ -214,6 +258,64 @@ contract StakingManager is IStakingManager, Initializable, OwnableUpgradeable {
staking.slashRunner(_indexer, _amount);
}

/**
* @dev Calculate current era progress as a percentage (in perMill)
* @return Progress value (0-PER_MILL, where PER_MILL = 100%)
*/
function _calculateEraProgress() internal view returns (uint256) {
IEraManager eraManager = IEraManager(settings.getContractAddress(SQContracts.EraManager));

uint256 eraStartTime = eraManager.eraStartTime();
uint256 eraPeriod = eraManager.eraPeriod();

uint256 elapsed = block.timestamp - eraStartTime;

// Prevent overflow: if elapsed >= eraPeriod, return 100%
if (elapsed >= eraPeriod) {
return PER_MILL;
}

return MathUtil.mulDiv(elapsed, PER_MILL, eraPeriod);
}

Comment thread
ianhe8x marked this conversation as resolved.
/**
* @dev Get remaining instant delegation quota for a delegator
* @param delegator The delegator address
* @return Remaining quota amount
*/
function _getRemainingQuota(address delegator) internal view returns (uint256) {
IEraManager eraManager = IEraManager(settings.getContractAddress(SQContracts.EraManager));
Staking staking = Staking(settings.getContractAddress(SQContracts.Staking));

uint256 currentEra = eraManager.eraNumber();
return staking.getInstantQuotaRemaining(delegator, currentEra);
}

/**
* @dev Apply instant delegation to rewards system
* @param delegator The delegator address
* @param runner The runner address
*/
function _applyInstantDelegation(address delegator, address runner) internal {
IRewardsStaking rewardsStaking = IRewardsStaking(
settings.getContractAddress(SQContracts.RewardsStaking)
);
rewardsStaking.applyRedelegation(runner, delegator);
}

/**
* @dev Consume instant quota for a delegator
* @param delegator The delegator address
* @param amount The amount to consume
*/
function _consumeInstantQuota(address delegator, uint256 amount) internal {
IEraManager eraManager = IEraManager(settings.getContractAddress(SQContracts.EraManager));
Staking staking = Staking(settings.getContractAddress(SQContracts.Staking));

uint256 currentEra = eraManager.eraNumber();
staking.updateInstantQuotaUsed(delegator, currentEra, amount);
}

// -- Views --

function _getCurrentDelegationAmount(
Expand Down
31 changes: 31 additions & 0 deletions contracts/interfaces/IStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,39 @@ enum UnbondType {
Merge
}

/**
* @dev Instant delegation quota tracking. One per Delegator.
* Tracks quota usage within current era, auto-resets on era change.
*/
struct InstantQuotaUsage {
uint256 era; // era of quota usage
uint256 amount; // quota used in this era
}

interface IStaking {
function lockedAmount(address _delegator) external view returns (uint256);

function unbondCommission(address _runner, uint256 _amount) external;

function addDelegation(
address _source,
address _runner,
uint256 _amount,
bool instant
) external;

function transferDelegationTokens(address _source, uint256 _amount) external;

function updateInstantQuotaUsed(address delegator, uint256 era, uint256 amount) external;

function setInstantDelegationParams(uint256 _perEraQuota, uint256 _windowPercent) external;

function getInstantQuotaRemaining(
address delegator,
uint256 era
) external view returns (uint256);

function instantDelegationQuota() external view returns (uint256);

function instantEraWindowPercent() external view returns (uint256);
}
21 changes: 20 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,24 @@
"prettier --write --plugin=prettier-plugin-solidity"
]
},
"packageManager": "yarn@3.6.4"
"resolutions": {
"@ethersproject/abstract-provider": "~5.5.1",
"@ethersproject/abstract-signer": "~5.5.0",
"@ethersproject/address": "~5.5.0",
"@ethersproject/bignumber": "~5.5.0",
"@ethersproject/bytes": "~5.5.0",
"@ethersproject/constants": "~5.5.0",
"@ethersproject/contracts": "~5.5.0",
"@ethersproject/hash": "~5.5.0",
"@ethersproject/keccak256": "~5.5.0",
"@ethersproject/logger": "~5.5.0",
"@ethersproject/networks": "~5.5.2",
"@ethersproject/properties": "~5.5.0",
"@ethersproject/providers": "~5.5.3",
"@ethersproject/signing-key": "~5.5.0",
"@ethersproject/strings": "~5.5.0",
"@ethersproject/transactions": "~5.5.0",
"@ethersproject/wallet": "~5.5.0"
},
"packageManager": "yarn@4.10.3"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify Yarn 4 compatibility across the project.

Upgrading from Yarn 3.6.4 to 4.10.3 is a major version change with potential breaking changes in:

  • Plugin/extension APIs
  • Configuration format (.yarnrc.yml)
  • Command-line interface
  • Workspace protocol handling

Ensure all CI/CD pipelines, developer environments, and scripts are compatible with Yarn 4.


🏁 Script executed:

#!/bin/bash
# Description: Verify yarn configuration and check for potential incompatibilities
# Expected: Find .yarnrc.yml and scripts that invoke yarn commands

echo "=== Checking .yarnrc.yml configuration ==="
fd -t f '.yarnrc.yml' --exec cat {}

echo -e "\n=== Finding scripts that invoke yarn ==="
rg -n 'yarn ' --type=sh --type=json

echo -e "\n=== Checking package.json scripts for yarn-specific features ==="
cat package.json | jq '.scripts'

Length of output: 1873


I'll continue verification by checking for Yarn configuration directory, CI/CD pipelines, and researching Yarn 4 breaking changes.


🏁 Script executed:

#!/bin/bash
# Check for .yarn directory and CI/CD configuration
echo "=== Checking for .yarn directory ==="
fd -t d '^\.yarn$'

echo -e "\n=== Checking for CI/CD pipeline files ==="
fd -t f '\.(github|gitlab-ci|circle|travis)' -x echo {}

echo -e "\n=== Checking for yarn lock file and its format ==="
fd -t f 'yarn.lock' -x head -20 {}

echo -e "\n=== Searching for yarn plugin references ==="
rg -n '@yarn|yarnpkg' --type=json -C 2

Length of output: 1072


🌐 Web query:

Yarn 4 major breaking changes from Yarn 3

💡 Result:

Major breaking changes when upgrading from Yarn 3.x → Yarn 4.x:

  • Minimum Node.js raised to 18+. [1]
  • New projects no longer enable Zero‑Install by default. [1]
  • New projects created with yarn init use Corepack instead of a yarnPath (per‑project yarn binary). [1]
  • Many official plugins (typescript, interactive-tools, etc.) are included by default. [1]
  • yarn workspaces foreach has a slightly altered command syntax (may require script updates). [1]

Sources: Yarn 4 release notes / breaking changes. [1]


🏁 Script executed:

#!/bin/bash
# Check Node.js version requirement and .github workflows with corrected commands
echo "=== Checking Node.js version requirement in package.json ==="
cat package.json | jq '.engines'

echo -e "\n=== Checking for .nvmrc ==="
fd -t f '.nvmrc' -x cat {}

echo -e "\n=== Checking for .github directory (with -H flag) ==="
fd -t d -H '^\.github$'

echo -e "\n=== Checking for any github workflows ==="
fd -t f -H 'workflows' --exec ls -la {} \;

echo -e "\n=== Checking for script files that call yarn ==="
fd -t f '\.sh$' -x grep -l 'yarn' {} \;

Length of output: 251


🌐 Web query:

Yarn 4 Node.js version requirement compatibility

💡 Result:

Short answer: Yarn 4 requires Node.js 18 or newer (the release notes state Node.js 18+; the changelog notes support dropped for versions lower than 18.12). [1][2]

Notes:

  • Yarn 4 is distributed with Corepack-friendly defaults (use packageManager / corepack to install). [1].

Sources:

  • Yarn 4 release post. [1]
  • Yarn changelog (4.0.0: dropped support for Node < 18.12). [2]

🏁 Script executed:

#!/bin/bash
# Direct check for GitHub Actions workflows and .github contents
echo "=== Checking .github directory contents ==="
find .github -type f 2>/dev/null | head -20

echo -e "\n=== Checking for GitHub Actions workflow files ==="
fd -H -t f '\.(yml|yaml)$' '.github/workflows/'

echo -e "\n=== Checking .gitignore for node version hints ==="
cat .gitignore 2>/dev/null | grep -i 'node\|nvmrc' || echo "No node-related patterns found"

echo -e "\n=== Checking for Corepack configuration ==="
cat .npmrc 2>/dev/null || echo "No .npmrc found"

Length of output: 638


🏁 Script executed:

#!/bin/bash
# Check Node.js and yarn version setup in workflows
echo "=== Checking workflow files for Node.js and yarn setup ==="
for workflow in .github/workflows/*.yml; do
  echo -e "\n--- $workflow ---"
  grep -E 'node-version|setup-node|yarn' "$workflow" | head -10
done

echo -e "\n=== Full check of pr.yml (main CI pipeline) ==="
cat .github/workflows/pr.yml | grep -A5 -B5 'node-version\|setup-node' || echo "Checking entire pr.yml structure..."
cat .github/workflows/pr.yml

Length of output: 2753


🌐 Web query:

GitHub Actions setup-node v1 Corepack packageManager Yarn 4 support

💡 Result:

Short answer: setup-node@v1 does not automatically enable or configure Corepack/Yarn‑4 for you — you must enable Corepack (or install Yarn 4) in your workflow before running yarn. See Corepack docs and examples (add a step: run: corepack enable). [1][2][3]

Recommended minimal workflow change:

  • run: corepack enable
  • run: corepack use yarn@4 # optional: pin a specific Yarn 4.x
  • run: yarn install --frozen-lockfile

References

  • actions/setup-node docs & issues (shows CI still has Yarn 1 until Corepack is enabled). [1]
  • Corepack README (how to enable/use Corepack and supported commands). [2]
  • Example CI usages recommending corepack enable before Yarn 4 installs. [3]

Add corepack enable to all GitHub Actions workflows before running yarn commands.

The package.json upgrade to Yarn 4.10.3 is specified via the packageManager field, but GitHub Actions setup-node@v1 does not automatically enable Corepack. All 7 workflows (.github/workflows/{pr,deploy,docs-deploy,fuzz,prerelease,release,upgrade}.yml) will continue using the bundled Yarn 1.x instead of 4.10.3 unless explicitly enabled.

Add a step before each yarn invocation:

- run: corepack enable

This ensures CI uses the specified yarn@4.10.3 from package.json rather than defaulting to Yarn 1.x.

🤖 Prompt for AI Agents
In package.json around line 123, CI will still use bundled Yarn 1.x because
setup-node@v1 does not enable Corepack; add a step to each GitHub Actions
workflow file (.github/workflows/pr.yml, deploy.yml, docs-deploy.yml, fuzz.yml,
prerelease.yml, release.yml, upgrade.yml) before any yarn commands to run
corepack enable so the runner uses the Yarn version declared in package.json
(yarn@4.10.3); update each workflow to insert that single-step run: corepack
enable immediately prior to any yarn invocation.

}
Loading