Skip to content

solidity gringotts#26

Open
codchen wants to merge 14 commits into
mainfrom
tony/solidity
Open

solidity gringotts#26
codchen wants to merge 14 commits into
mainfrom
tony/solidity

Conversation

@codchen
Copy link
Copy Markdown
Collaborator

@codchen codchen commented Feb 2, 2026

Summary

Ports the CosmWasm Gringotts vesting contract to Solidity for deployment on Sei's EVM. Uses native SEI tokens and integrates directly with Sei's staking and distribution precompiles.

Key Features

  • Native SEI Support: Works with native SEI tokens (not ERC20), sent via msg.value during deployment
  • Sei Precompile Integration: Direct integration with Sei's staking (0x1005) and distribution (0x1007) precompiles
  • Upgradeable Contract: UUPS proxy pattern with multi-sig admin approval for upgrades
  • Migration Tooling: Scripts to export state from existing CosmWasm contracts and deploy Solidity equivalents

Changes

New Files:

  • solidity/contracts/Gringotts.sol - Main upgradeable vesting contract
  • solidity/contracts/GringottsFactory.sol - Factory for deploying proxy instances
  • solidity/interfaces/IStaking.sol - Sei staking precompile interface
  • solidity/interfaces/IDistribution.sol - Sei distribution precompile interface
  • solidity/contracts/mocks/ - Mock contracts for testing
  • solidity/scripts/migration/ - Migration scripts for CosmWasm → Solidity
  • solidity/test/Gringotts.test.js - Test suite (15 passing)

Configuration:

  • solidity/package.json - Hardhat project with dependencies
  • solidity/hardhat.config.js - Compiler settings (0.8.24, viaIR, Sei networks)
  • .gitignore - Added node_modules/ and **/cache

Upgrade Process

Contract upgrades require multi-sig admin approval:

  1. Admin calls proposeUpgrade(newImplementation)
  2. Other admins vote via voteProposal(proposalId)
  3. Once threshold reached, admin calls processProposal(proposalId)

Migration from CosmWasm

Export existing contract state

node scripts/migration/export-cosmwasm.js sei1contract... --output export.json

Convert addresses (sei1... → 0x...)

seid q evm sei-addr sei1admin...

Deploy to Sei EVM

EXPORT_FILE=export.json npx hardhat run scripts/migration/deploy-from-export.js --network sei


Note

High Risk
High risk because this introduces new upgradeable Solidity contracts that custody and transfer native SEI, interact with Sei staking/distribution/gov precompiles, and implement admin-controlled upgrade/emergency-withdraw flows.

Overview
Adds a new solidity/ Hardhat project implementing Gringotts as an upgradeable (UUPS) native-SEI vesting contract with admin multi-sig proposals, operator-only staking actions, reward withdrawals via Sei precompiles, and an admin-approved emergency withdrawal path.

Introduces GringottsFactory for deploying initialized ERC1967 proxies, Sei precompile interfaces (IStaking, IDistribution, IGov), and mock precompile contracts for testing; adds docs and deployment/migration scripts.

Wires up CI via a new Solidity GitHub Actions workflow to npm ci, compile, and run tests (with a Sei docker image), updates .gitignore for Node artifacts, and bumps the Rust crate version to 0.1.11.

Reviewed by Cursor Bugbot for commit 2d4e958. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread solidity/contracts/Gringotts.sol Outdated
Comment thread solidity/contracts/Gringotts.sol
Comment thread solidity/contracts/Gringotts.sol Outdated
Comment thread solidity/contracts/Gringotts.sol Outdated
Comment thread solidity/contracts/Gringotts.sol
Comment thread solidity/contracts/Gringotts.sol
Comment thread solidity/contracts/Gringotts.sol Outdated

function _coinAmountToWei(IDistribution.Coin memory coin) internal pure returns (uint256) {
return coin.amount / DECIMAL_USEI_PER_WEI;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reward conversion ignores coin decimals field

Low Severity

_coinAmountToWei divides coin.amount by the hardcoded DECIMAL_USEI_PER_WEI (1e6) without consulting coin.decimals or coin.denom. This produces the correct result only when the precompile returns usei with exactly 18-decimal precision. If the distribution precompile ever returns a coin with a different decimals value or a non-usei denomination, the conversion silently produces an incorrect result, leading to over- or under-counting of staking rewards in all reward-tracking paths.

Fix in Cursor Fix in Web

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

@cursor
Copy link
Copy Markdown

cursor Bot commented May 19, 2026

PR Summary

High Risk
Large new Solidity surface area for vesting, staking reward withdrawals, governance proposals, and UUPS upgrades; bugs could affect fund custody and upgrade safety. Also introduces a new CI workflow that runs live Sei tests via Docker, increasing deployment/test coupling risk.

Overview
Adds a new solidity/ Hardhat project that ports Gringotts to Sei EVM: an upgradeable Gringotts vesting contract (native SEI via msg.value) with operator-managed staking/reward flows via Sei precompiles, plus admin multi-sig proposals for role changes, distribution-address updates, emergency withdrawal, upgrades, and governance voting.

Introduces GringottsFactory for deploying ERC1967 proxies, precompile interfaces (IStaking, IDistribution, IGov), and testing mocks. Adds a GitHub Actions Solidity workflow to install/compile/test against a pulled Sei Docker image, updates .gitignore for Node artifacts, and bumps the Rust crate version to 0.1.11.

Reviewed by Cursor Bugbot for commit 34219da. Bugbot is set up for automated code reviews on this repo. Configure here.

if (!success) revert TransferFailed();

emit UnlockedWithdrawn(unlockDistributionAddress, vestedAmount);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Vesting withdrawal can consume banked staking rewards

Medium Severity

initiateWithdrawUnlocked sends vestedAmount from the contract balance without accounting for bankedStakingRewards. When the liquid principal (balance minus banked rewards) is less than the requested vesting withdrawal — e.g. because some principal was staked — the transfer consumes SEI that belongs to banked staking rewards, routing those funds to unlockDistributionAddress instead of stakingRewardAddress. The corresponding _calculateWithdrawnRewards later returns less (capped by the now-reduced balance), so the reward address permanently loses that amount. initiateWithdrawReward correctly protects principal via min(bankedStakingRewards, balance), but initiateWithdrawUnlocked has no symmetric guard.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c6d172a. Configure here.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 34219da. Configure here.


function _calculateWithdrawnRewards() internal view returns (uint256) {
uint256 bankBalance = address(this).balance;
return bankedStakingRewards < bankBalance ? bankedStakingRewards : bankBalance;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Banked rewards miss implicit balance

Medium Severity

initiateWithdrawReward only forwards min(bankedStakingRewards, balance) via _calculateWithdrawnRewards, while the CosmWasm port derives banked rewards as contract balance minus principal reserved for vesting, staking, and unbonding. Reward wei that sits in the contract without updating bankedStakingRewards can remain stuck after initiateWithdrawReward.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 34219da. Configure here.

if (!success) revert DistributionFailed();

if (stakingRewardWithdrawAddressConfigured) {
_recordWithdrawnStakingRewards(pendingRewards);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Undelegate banks returned principal

Low Severity

When stakingRewardWithdrawAddressConfigured is true, reward withdrawals add pre-query withdrawableRewards or pendingRewards to withdrawnStakingRewards without checking that those wei actually left the distribution module to stakingRewardAddress, so accounting can exceed rewards truly paid out.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 34219da. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant