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
3 changes: 3 additions & 0 deletions solidity/contracts/Gringotts.sol
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ contract Gringotts is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable
error InvalidImplementation();
error UpgradeNotApproved();
error CannotRemoveLastAdmin();
error CannotRemoveLastOperator();
error DuplicateAddress();
error InvalidVoteOption();
error GovVoteFailed();
Expand Down Expand Up @@ -1086,6 +1087,8 @@ contract Gringotts is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable
if (!operators[operator]) {
return;
}
if (operatorCount <= 1) revert CannotRemoveLastOperator();

operators[operator] = false;
operatorCount--;
_removeAddressFromList(operatorList, operatorListIndexPlusOne, operator);
Expand Down
14 changes: 14 additions & 0 deletions solidity/contracts/mocks/GringottsV2Dummy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "../Gringotts.sol";

contract GringottsV2Dummy is Gringotts {
function dummyVersion() external pure returns (string memory) {
return "gringotts-v2-dummy";
}

function dummyNumber() external pure returns (uint256) {
return 2;
}
}
37 changes: 37 additions & 0 deletions solidity/deployments/gringotts-713714-1779165468088.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"deployedAt": "2026-05-19T04:37:48.088Z",
"network": "harbor-shortunbond-testnet",
"chainId": "713714",
"deployer": "0x9eFbcf57C4DE1b750749d840B9fB7CcF03e239Ac",
"configFile": "/Users/xiaoyuchen/repos/gringotts/solidity/scripts/deploy-and-create.example.json",
"implementation": "0xff7b70EE3ddB75C209d5A1c249eC9bC21c4f6aEF",
"factory": "0xEaaF4eF2Ef3E4926c236998301cb1248F6C19687",
"proxy": "0xE79F135F45D571352f7d570Da1E2a295f966A58B",
"createTransaction": "0x769a45778d56e729d5a49ca7f5ed6693ee889756c69ba9579ee8cfe5852ab184",
"params": {
"admins": [
"0x28fc161d44DeBA46E3d4aD2416dB84044035b02a",
"0x806465dc3e5eF9dC5AFf42a5b8744c94f0ddEc78",
"0x06868083F2B914e52dC09A6e22994EA8ff2FDa49"
],
"operators": [
"0xAE9Ed5c5397d74FC5FbF396d0e0de4663b4895B9"
],
"vestingTimestamps": [
"1779199200",
"1779242400",
"1779285600"
],
"vestingAmounts": [
"1000000000000000000",
"1000000000000000000",
"1000000000000000000"
],
"unlockDistributionAddress": "0x7fd42b44F08eC90Aa7C82f9C40235Dc66b86C201",
"stakingRewardAddress": "0x6199d949c97e818abd967EC9EcA3e89FFbE92C44",
"maxVotingPeriod": "300",
"adminVotingThresholdPercentage": "50",
"totalAmount": "3000000000000000000",
"vestingTotal": "3000000000000000000"
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Testnet deployment artifact committed with local path

Low Severity

A specific testnet deployment output file was committed to the repo. It contains a local filesystem path in configFile (/Users/xiaoyuchen/repos/...), deployer addresses, and transaction hashes from a one-off testnet deployment. The deployments/ directory is not in .gitignore, and loadScenarioConfig() in common.js auto-discovers the latest file from this directory, meaning this committed artifact will silently become the default proxy address for anyone running the scenario scripts.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2ecf9f1. Configure here.

37 changes: 37 additions & 0 deletions solidity/deployments/gringotts-713714-1779251801379.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"deployedAt": "2026-05-20T04:36:41.379Z",
"network": "harbor-shortunbond-testnet",
"chainId": "713714",
"deployer": "0x9eFbcf57C4DE1b750749d840B9fB7CcF03e239Ac",
"configFile": "/tmp/gringotts-deploy-test-config.json",
"implementation": "0x0b18CA044eABCBD5aC80357E9DF6EB28f5385750",
"factory": "0x732F65Ab5Ee3BD5496127705532360D7F84774dF",
"proxy": "0xD1984F3e3b3DbB7425EeCfD9309075355c27a55b",
"createTransaction": "0xf7860f5aea0eeee58a5bffc90998289a824dc6115a247c0c66a27446b4146f7a",
"params": {
"admins": [
"0x28fc161d44DeBA46E3d4aD2416dB84044035b02a",
"0x806465dc3e5eF9dC5AFf42a5b8744c94f0ddEc78",
"0x06868083F2B914e52dC09A6e22994EA8ff2FDa49"
],
"operators": [
"0xAE9Ed5c5397d74FC5FbF396d0e0de4663b4895B9"
],
"vestingTimestamps": [
"1779251685",
"1779251715",
"1779251745"
],
"vestingAmounts": [
"1000000000000000000",
"1000000000000000000",
"1000000000000000000"
],
"unlockDistributionAddress": "0x7fd42b44F08eC90Aa7C82f9C40235Dc66b86C201",
"stakingRewardAddress": "0x6199d949c97e818abd967EC9EcA3e89FFbE92C44",
"maxVotingPeriod": "15",
"adminVotingThresholdPercentage": "50",
"totalAmount": "3000000000000000000",
"vestingTotal": "3000000000000000000"
}
}
21 changes: 11 additions & 10 deletions solidity/scripts/deploy-and-create.example.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
{
"admins": [
"0x1111111111111111111111111111111111111111",
"0x2222222222222222222222222222222222222222"
"0x28fc161d44DeBA46E3d4aD2416dB84044035b02a",
"0x806465dc3e5eF9dC5AFf42a5b8744c94f0ddEc78",
"0x06868083F2B914e52dC09A6e22994EA8ff2FDa49"
],
"operators": [
"0x3333333333333333333333333333333333333333"
"0xAE9Ed5c5397d74FC5FbF396d0e0de4663b4895B9"
],
"vestingTimestamps": [
1893456000,
1924992000,
1956528000
1779199200,
1779242400,
1779285600
],
"vestingAmounts": [
"1000000000000000000",
"1000000000000000000",
"1000000000000000000"
],
"unlockDistributionAddress": "0x4444444444444444444444444444444444444444",
"stakingRewardAddress": "0x5555555555555555555555555555555555555555",
"maxVotingPeriod": 3600,
"adminVotingThresholdPercentage": 75,
"unlockDistributionAddress": "0x7fd42b44F08eC90Aa7C82f9C40235Dc66b86C201",
"stakingRewardAddress": "0x6199d949c97e818abd967EC9EcA3e89FFbE92C44",
"maxVotingPeriod": 300,
"adminVotingThresholdPercentage": 50,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Example config replaced with real testnet-specific values

Medium Severity

The deploy-and-create.example.json template file had its obvious placeholder addresses (0x1111..., 0x2222..., etc.) replaced with real testnet addresses, maxVotingPeriod lowered from 3600 to 300, and adminVotingThresholdPercentage lowered from 75 to 50. The deploy script (deploy-and-create.js) references this file by name as its example config. Replacing self-documenting placeholders with real values means someone following the usage instructions could accidentally deploy against these specific addresses and with reduced governance security parameters.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2ecf9f1. Configure here.

"totalAmount": "3000000000000000000"
}
153 changes: 153 additions & 0 deletions solidity/scripts/scenario-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Gringotts Scenario Scripts

These scripts exercise the deployed `Gringotts` proxy against the scenario list in `/Users/xiaoyuchen/Downloads/EVM Gringotts Testing.txt`.

They are review-safe by default. Without `EXECUTE=true`, scripts only run read/static-call checks and skip state-changing scenarios.

## One-Time Setup

Compile contracts first so the scripts can load current ABIs, including `GringottsV2Dummy`:

```bash
cd /Users/xiaoyuchen/repos/gringotts/solidity
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

README contains hardcoded local filesystem paths

Low Severity

The README contains developer-local absolute paths (/Users/xiaoyuchen/Downloads/EVM Gringotts Testing.txt on line 3 and /Users/xiaoyuchen/repos/gringotts/solidity on line 12) that are meaningless and broken for any other developer cloning the repo. These look like they were left in from local development.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2ecf9f1. Configure here.

npm run compile
```

The scripts resolve the proxy address in this order:

1. `PROXY_ADDRESS`
2. `SCENARIO_CONFIG.proxyAddress`
3. latest `deployments/gringotts-*.json`

Create an ignored local config for signer keys and validator addresses:

```bash
cp scripts/scenario-tests/scenario.config.example.json scripts/scenario-tests/scenario.local.json
```

Then fill in signer private keys and validator addresses:

- `adminPrivateKeys`: enough current admin keys to pass proposals.
- `operatorPrivateKey`: a current operator key for staking/withdrawal flows.
- `implementationDeployerPrivateKey`: optional funded key for `upgrade-tests.js`; it does not need admin permission.
- `distribution.stakingRewardPrivateKey`: optional key for the contract's current staking reward address. If that address is not yet associated, mutating reward-address proposal tests use the default Hardhat signer, or `distribution.associationFunderPrivateKey` when set, to fund it and then send a tiny transaction back so Sei creates the address association. Do not commit this key.

```bash
SCENARIO_CONFIG=scripts/scenario-tests/scenario.local.json \
npx hardhat --config hardhat.harbor-shortunbond.config.js run scripts/scenario-tests/access-tests.js --network harbor-shortunbond-testnet
```

Do not commit `scenario.local.json`.

## Static Review Mode

Run scripts without `EXECUTE=true` first. This mode checks read-only state and `staticCall` reverts, then skips mutating scenarios.

```bash
SCENARIO_CONFIG=scripts/scenario-tests/scenario.local.json \
npx hardhat --config hardhat.harbor-shortunbond.config.js run scripts/scenario-tests/access-tests.js --network harbor-shortunbond-testnet
```

Run all scenario groups in static/review mode:

```bash
for script in \
access-tests.js \
vesting-tests.js \
staking-operation-tests.js \
reward-tests.js \
governance-admin-tests.js \
upgrade-tests.js
do
SCENARIO_CONFIG=scripts/scenario-tests/scenario.local.json \
npx hardhat --config hardhat.harbor-shortunbond.config.js run "scripts/scenario-tests/$script" --network harbor-shortunbond-testnet
done
```

## Execution Guards

- `EXECUTE=true`: allow state-changing transactions.
- `ALLOW_DESTRUCTIVE=true`: allow irreversible/destructive checks such as emergency withdrawal.
- `WAIT_FOR_EXPIRY=true`: allow scripts to wait through proposal expiration windows.
- `RUN_STRESS=true`: allow repeated 7-entry unbonding/redelegation stress loops.

## Mutating Runs

Run one group at a time when sending transactions. Example:

```bash
EXECUTE=true \
SCENARIO_CONFIG=scripts/scenario-tests/scenario.local.json \
npx hardhat --config hardhat.harbor-shortunbond.config.js run scripts/scenario-tests/access-tests.js --network harbor-shortunbond-testnet
```

If the current staking reward address is not associated yet, provide its key when running reward proposal flows. The script preserves the contract's reward address, funds that address from the default Hardhat signer, and sends a tiny transaction back so Sei creates the association before processing the proposal:

```bash
EXECUTE=true \
STAKING_REWARD_PRIVATE_KEY=0x... \
SCENARIO_CONFIG=scripts/scenario-tests/scenario.local.json \
npx hardhat --config hardhat.harbor-shortunbond.config.js run scripts/scenario-tests/reward-tests.js --network harbor-shortunbond-testnet
```

Run the live upgrade/migration-style check. This deploys `GringottsV2Dummy`, proposes an upgrade, votes/processes it, then calls the new dummy functions through the existing proxy:

```bash
EXECUTE=true \
SCENARIO_CONFIG=scripts/scenario-tests/scenario.local.json \
npx hardhat --config hardhat.harbor-shortunbond.config.js run scripts/scenario-tests/upgrade-tests.js --network harbor-shortunbond-testnet
```

Run expiration scenarios only when you are willing to wait through `maxVotingPeriod`:

```bash
EXECUTE=true WAIT_FOR_EXPIRY=true \
SCENARIO_CONFIG=scripts/scenario-tests/scenario.local.json \
npx hardhat --config hardhat.harbor-shortunbond.config.js run scripts/scenario-tests/governance-admin-tests.js --network harbor-shortunbond-testnet
```

Run destructive/stress scenarios only on a disposable deployment:

```bash
EXECUTE=true ALLOW_DESTRUCTIVE=true RUN_STRESS=true \
SCENARIO_CONFIG=scripts/scenario-tests/scenario.local.json \
npx hardhat --config hardhat.harbor-shortunbond.config.js run scripts/scenario-tests/vesting-tests.js --network harbor-shortunbond-testnet
```

## Unit Tests

The Hardhat unit suite includes a local upgrade test that upgrades through admin proposals to `GringottsV2Dummy` and calls the dummy functions through the proxy.

Run only the upgrade unit test:

```bash
npx hardhat test test/Gringotts.test.js --grep "GringottsV2Dummy"
```

Run the full Solidity test suite:

```bash
npm test
```

## Script Groups

- `access-tests.js`: operator/admin access, direct upgrade blocking, proposal lifecycle basics.
- `vesting-tests.js`: vesting reads and guarded unlocked/emergency withdrawal flows.
- `staking-operation-tests.js`: delegate, undelegate, redelegate, invalid validator, truncation, entry-limit scenarios.
- `reward-tests.js`: reward withdrawal and distribution address update flows.
- `governance-admin-tests.js`: admin/distribution/gov proposals, expiration, threshold, last-admin/last-operator invariants.
- `upgrade-tests.js`: deploys `GringottsV2Dummy`, upgrades by admin proposal, then calls the new dummy function through the proxy.

Some scenarios depend on live-chain timing or available rewards. The scripts explicitly skip those when prerequisites are missing rather than pretending the condition was tested.

## Validator Addresses

The Harbor short-unbond testnet currently has these bonded validators:

```text
seivaloper1z7dlhv79r45ulcnnumxle67ven8n65hcaxvwea
seivaloper1yp96dyhkjndszsyf7mv55tck34c5vgue5l2rxv
seivaloper1jjy6d7kpyphcgtkgjsgrp8624m2uq3cp2gauhz
seivaloper15c6rdhx97mc7uuhl94v3ramslcpt6xcjeqqrx9
```
Loading