Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
39 changes: 14 additions & 25 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:

jobs:
check:
name: monad-foundry project
name: foundry project
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -19,30 +19,12 @@ jobs:
persist-credentials: false
submodules: recursive

- name: Restore cached monad-foundry binaries
id: foundry-cache
uses: actions/cache@v4
# Upstream Foundry >= 1.8.0 ships first-class Monad execution (`--network monad`),
# including the staking precompile at 0x1000. Pinned so CI matches local dev.
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
path: ~/.foundry/bin
# Bump this version to refresh monad-foundry binaries.
key: monad-foundry-${{ runner.os }}-stable-monad-v1

- name: Install monad-foundry (cache miss)
if: steps.foundry-cache.outputs.cache-hit != 'true'
env:
# Pinned to a specific commit on category-labs/foundry@monad to avoid
# piping an unverified remote script into the shell.
INSTALLER_COMMIT: 5db76009a572e9d8b64fa7dd0ac899e7c4cb5b7d
INSTALLER_SHA256: 72b447ab1463b1f9ee1d7a20263495777565a5c0f7131e4f515af781e8a643de
run: |
export FOUNDRY_DIR="$HOME/.foundry"
curl -fsSL "https://raw.githubusercontent.com/category-labs/foundry/$INSTALLER_COMMIT/foundryup/install" -o installer.sh
echo "$INSTALLER_SHA256 installer.sh" | sha256sum --check
bash installer.sh
"$FOUNDRY_DIR/bin/foundryup" -n monad

- name: Add monad-foundry to PATH
run: echo "$HOME/.foundry/bin" >> "$GITHUB_PATH"
version: v1.8.3

- name: Show Forge version
run: forge --version
Expand All @@ -53,5 +35,12 @@ jobs:
- name: Build
run: forge build --sizes

- name: Test
- name: Unit tests
run: forge test -vvv

# Real staking precompile. REQUIRE_MONAD_PRECOMPILE makes the integration suite fail
# instead of silently skipping if the precompile is not present.
- name: Integration tests (Monad network)
env:
REQUIRE_MONAD_PRECOMPILE: "true"
run: forge test --network monad --match-contract Integration -vvv
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/monad-std"]
path = lib/monad-std
url = https://github.com/category-labs/monad-std
3 changes: 3 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ solc_version = "0.8.28"
evm_version = "prague"
optimizer = true
optimizer_runs = 200
# Required: destructuring the 12-value `IMonadStaking.getValidator` return is stack-too-deep
# under the legacy codegen pipeline.
via_ir = true
bytecode_hash = "none"

[fuzz]
Expand Down
1 change: 1 addition & 0 deletions lib/monad-std
Submodule monad-std added at 2e79c4
12 changes: 10 additions & 2 deletions src/ValidatorMetadata.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,18 @@ contract ValidatorMetadata is IValidatorMetadata {
/// @param authority The caller that performed the deletion (validator's authority or an approved delegate).
event MetadataDeleted(uint64 indexed validatorId, address indexed authority);

/// Returns the validator's authority from the staking precompile.
/// @dev Destructuring the 12-value `getValidator` return only compiles with `via_ir = true`
/// (see foundry.toml); the legacy pipeline hits "stack too deep". Reverts from the
/// precompile and malformed return data bubble up through the high-level call.
function _authorityOf(uint64 validatorId) private returns (address authority) {
(authority,,,,,,,,,,,) = IMonadStaking(STAKING_PRECOMPILE).getValidator(validatorId);
}

/// Restricts to the validator's current authority, or an address the authority has approved
/// for this specific validator.
modifier onlyAuthorizedWriter(uint64 validatorId) {
address authority = IMonadStaking(STAKING_PRECOMPILE).getValidator(validatorId).authority;
Comment thread
mijovic marked this conversation as resolved.
address authority = _authorityOf(validatorId);
require(msg.sender == authority || _approvals[validatorId][authority][msg.sender], Unauthorized());
_;
}
Expand Down Expand Up @@ -142,7 +150,7 @@ contract ValidatorMetadata is IValidatorMetadata {
/// @param approved True to grant, false to revoke.
function setApproval(uint64 validatorId, address delegate, bool approved) external {
require(delegate != address(0) && delegate != msg.sender, InvalidDelegate());
address authority = IMonadStaking(STAKING_PRECOMPILE).getValidator(validatorId).authority;
address authority = _authorityOf(validatorId);
require(msg.sender == authority, Unauthorized());
if (_approvals[validatorId][authority][delegate] == approved) return;
_approvals[validatorId][authority][delegate] = approved;
Expand Down
208 changes: 88 additions & 120 deletions src/interfaces/IMonadStaking.sol
Original file line number Diff line number Diff line change
@@ -1,132 +1,100 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
pragma solidity ^0.8.15;

/**
* @title IMonadStaking
* @notice Interface for the Monad Staking Precompile at address 0x0000000000000000000000000000000000001000
* @dev This precompile manages validator registration, delegation, and rewards
*/
interface IMonadStaking {
/**
* @notice Complete validator information from the precompile
* @dev Matches Python SDK format: 12 fields returned from getValidator
* @param authority Address that controls this validator
* @param flags Validator flags/status
* @param consensusStake Stake used for consensus
* @param snapshotStake Snapshot stake value
* @param stake Total stake delegated to this validator
* @param accRewardPerToken Accumulator value for reward calculations
* @param consensusCommission Commission rate for consensus
* @param snapshotCommission Commission rate snapshot
* @param commission Current commission rate
* @param unclaimedRewards Rewards earned but not yet claimed
* @param secpPubKey SECP256k1 public key for execution layer
* @param blsPubKey BLS public key for consensus layer
*/
struct ValidatorInfo {
address authority;
uint256 flags;
uint256 consensusStake;
uint256 snapshotStake;
uint256 stake;
uint256 accRewardPerToken;
uint256 consensusCommission;
uint256 snapshotCommission;
uint256 commission;
uint256 unclaimedRewards;
bytes secpPubKey;
bytes blsPubKey;
}

/**
* @notice Delegator information for a specific validator
* @param stake Amount of MON delegated
* @param accumulator Accumulator value at time of last update
* @param unclaimedRewards Rewards earned but not yet claimed
*/
struct DelegatorInfo {
uint256 stake;
uint256 accumulator;
uint256 unclaimedRewards;
}

/**
* @notice Register a new validator with SECP and BLS public keys
* @return success Whether the registration was successful
*/
function addValidator() external returns (bool success);

/**
* @notice Delegate MON tokens to a validator
* @param validatorId The ID of the validator to delegate to
* @return success Whether the delegation was successful
*/
function addValidator(bytes calldata payload, bytes calldata signedSecpMessage, bytes calldata signedBlsMessage)
external
payable
returns (uint64 validatorId);

function delegate(uint64 validatorId) external payable returns (bool success);

/**
* @notice Initiate undelegation of staked MON
* @param validatorId The ID of the validator to undelegate from
* @param amount Amount of MON to undelegate
* @param withdrawalType Type of withdrawal (0 = standard, 1 = expedited)
* @return success Whether the undelegation was initiated successfully
*/
function undelegate(uint64 validatorId, uint256 amount, uint8 withdrawalType) external returns (bool success);

/**
* @notice Complete undelegation after withdrawal delay
* @param validatorId The ID of the validator
* @param withdrawalType Type of withdrawal that was initiated
* @return success Whether the withdrawal was successful
*/
function withdraw(uint64 validatorId, uint8 withdrawalType) external returns (bool success);

/**
* @notice Convert accumulated rewards into additional stake
* @param validatorId The ID of the validator
* @return success Whether compounding was successful
*/
function undelegate(uint64 validatorId, uint256 amount, uint8 withdrawId) external returns (bool success);

function compound(uint64 validatorId) external returns (bool success);

/**
* @notice Claim accumulated rewards
* @param validatorId The ID of the validator
* @return success Whether the claim was successful
*/
function withdraw(uint64 validatorId, uint8 withdrawId) external returns (bool success);

function claimRewards(uint64 validatorId) external returns (bool success);

/**
* @notice Change validator commission rate
* @param validatorId The ID of the validator
* @param newCommission New commission rate in basis points
* @return success Whether the commission change was successful
*/
function changeCommission(uint64 validatorId, uint256 newCommission) external returns (bool success);

/**
* @notice Distribute external rewards (tips) to delegators
* @param validatorId The ID of the validator
* @return success Whether the reward distribution was successful
*/
function changeCommission(uint64 validatorId, uint256 commission) external returns (bool success);

function externalReward(uint64 validatorId) external payable returns (bool success);

/**
* @notice Get complete validator information
* @param validatorId The ID of the validator
* @return info Complete validator information
*/
function getValidator(uint64 validatorId) external returns (ValidatorInfo memory info);

/**
* @notice Get delegator information for a specific validator
* @param validatorId The ID of the validator
* @param delegator Address of the delegator
* @return info Delegator stake and rewards information
*/
function getDelegator(uint64 validatorId, address delegator) external returns (DelegatorInfo memory info);

/**
* @notice Get current epoch information
* @return epoch Current epoch number
*/
function getEpoch() external returns (uint256 epoch);
function getValidator(uint64 validatorId)
external
returns (
address authAddress,
uint64 flags,
uint256 stake,
uint256 accRewardPerToken,
uint256 commission,
uint256 unclaimedRewards,
uint256 consensusStake,
uint256 consensusCommission,
uint256 snapshotStake,
uint256 snapshotCommission,
bytes memory secpPubkey,
bytes memory blsPubkey
);

function getDelegator(uint64 validatorId, address delegator)
external
returns (
uint256 stake,
uint256 accRewardPerToken,
uint256 unclaimedRewards,
uint256 deltaStake,
uint256 nextDeltaStake,
uint64 deltaEpoch,
uint64 nextDeltaEpoch
);

function getWithdrawalRequest(uint64 validatorId, address delegator, uint8 withdrawId)
external
returns (uint256 withdrawalAmount, uint256 accRewardPerToken, uint64 withdrawEpoch);

function getConsensusValidatorSet(uint32 startIndex)
external
returns (bool isDone, uint32 nextIndex, uint64[] memory valIds);

function getSnapshotValidatorSet(uint32 startIndex)
external
returns (bool isDone, uint32 nextIndex, uint64[] memory valIds);

function getExecutionValidatorSet(uint32 startIndex)
external
returns (bool isDone, uint32 nextIndex, uint64[] memory valIds);

function getDelegations(address delegator, uint64 startValId)
external
returns (bool isDone, uint64 nextValId, uint64[] memory valIds);

function getDelegators(uint64 validatorId, address startDelegator)
external
returns (bool isDone, address nextDelegator, address[] memory delegators);

function getEpoch() external returns (uint64 epoch, bool inEpochDelayPeriod);

function getProposerValId() external returns (uint64 val_id);

function syscallOnEpochChange(uint64 epoch) external;

function syscallReward(address blockAuthor) external;

function syscallSnapshot() external;

event ValidatorRewarded(uint64 indexed validatorId, address indexed from, uint256 amount, uint64 epoch);
event ValidatorCreated(uint64 indexed validatorId, address indexed authAddress, uint256 commission);
event ValidatorStatusChanged(uint64 indexed validatorId, uint64 flags);
event Delegate(uint64 indexed validatorId, address indexed delegator, uint256 amount, uint64 activationEpoch);
event Undelegate(
uint64 indexed validatorId, address indexed delegator, uint8 withdrawId, uint256 amount, uint64 activationEpoch
);
event Withdraw(
uint64 indexed validatorId, address indexed delegator, uint8 withdrawId, uint256 amount, uint64 withdrawEpoch
);
event ClaimRewards(uint64 indexed validatorId, address indexed delegator, uint256 amount, uint64 epoch);
event CommissionChanged(uint64 indexed validatorId, uint256 oldCommission, uint256 newCommission);
event EpochChanged(uint64 oldEpoch, uint64 newEpoch);
}
Loading
Loading