-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
5,640 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
name: Format Checks | ||
|
||
on: | ||
push: | ||
schedule: | ||
- cron: '0 */4 * * *' # every 4 hours, perpetual fuzz testing | ||
|
||
jobs: | ||
run-format: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
|
||
- name: add node toolchain | ||
uses: actions/[email protected] | ||
with: | ||
node-version: 16.x | ||
|
||
- name: cache node_modules | ||
uses: actions/cache@v2 | ||
with: | ||
path: | | ||
./node_modules | ||
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }} | ||
|
||
- name: install node dependencies | ||
run: yarn | ||
|
||
- name: run formatting checks | ||
run: yarn format:check |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
name: Lint Checks | ||
|
||
on: | ||
push: | ||
schedule: | ||
- cron: '0 */4 * * *' # every 4 hours, perpetual fuzz testing | ||
|
||
jobs: | ||
run-lint: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
|
||
- name: add node toolchain | ||
uses: actions/[email protected] | ||
with: | ||
node-version: 16.x | ||
|
||
- name: cache node_modules | ||
uses: actions/cache@v2 | ||
with: | ||
path: | | ||
./node_modules | ||
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }} | ||
|
||
- name: install node dependencies | ||
run: yarn | ||
|
||
- name: run linting checks | ||
run: yarn lint |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
name: Mythril Checks | ||
on: | ||
push: | ||
pull_request: | ||
|
||
jobs: | ||
run-mythril: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
with: | ||
submodules: recursive | ||
|
||
- name: StakingContract | ||
uses: ./actions/mythril | ||
id: staking-contract | ||
with: | ||
contract: './contracts/src/StakingContract' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
name: Tests | ||
on: | ||
push: | ||
pull_request: | ||
|
||
jobs: | ||
run-tests: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v2 | ||
with: | ||
submodules: recursive | ||
- name: Install Foundry | ||
uses: onbjerg/foundry-toolchain@v1 | ||
with: | ||
version: nightly | ||
|
||
- run: forge test --force -vvv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
cache/ | ||
out/ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"printWidth": 120 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "solhint:default" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.t.sol |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# 🥩 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
|
||
# action.yml | ||
name: 'Mythril' | ||
description: 'Run Mythril' | ||
inputs: | ||
contract: | ||
description: 'Contract to test' | ||
required: true | ||
runs: | ||
using: 'docker' | ||
image: 'mythril/myth' | ||
args: | ||
- '-v' | ||
- '4' | ||
- 'analyze' | ||
- ${{ inputs.contract }} | ||
- '--max-depth' | ||
- '15' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "staking-contracts", | ||
"version": "1.0.0", | ||
"dependencies": { | ||
"prettier": "2.6.0", | ||
"prettier-plugin-solidity": "1.0.0-beta.19", | ||
"solhint": "3.3.7" | ||
}, | ||
"scripts": { | ||
"format": "prettier --write ./src", | ||
"format:check": "prettier --check ./src", | ||
"lint": "solhint 'src/**/*.sol'", | ||
"test": "forge" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
ds-test/=lib/ds-test/src/ | ||
forge-std/=lib/forge-std/src/ | ||
solmate/=lib/solmate/src/ |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.10; | ||
|
||
import "./libs/StateLib.sol"; | ||
import "./libs/UintLib.sol"; | ||
import "./libs/BytesLib.sol"; | ||
|
||
import "./interfaces/IDepositContract.sol"; | ||
import "./test/console.sol"; | ||
|
||
contract StakingContract { | ||
using StateLib for bytes32; | ||
|
||
bytes32 internal constant FEE_SLOT = keccak256("StakingContract.fee"); | ||
bytes32 internal constant ADMIN_SLOT = keccak256("StakingContract.admin"); | ||
bytes32 internal constant VERSION_SLOT = keccak256("StakingContract.version"); | ||
bytes32 internal constant SIGNATURES_SLOT = keccak256("StakingContract.signatures"); | ||
bytes32 internal constant PUBLIC_KEYS_SLOT = keccak256("StakingContract.publicKeys"); | ||
bytes32 internal constant VALIDATOR_COUNT_SLOT = keccak256("StakingContract.validatorCount"); | ||
bytes32 internal constant DEPOSIT_CONTRACT_SLOT = keccak256("StakingContract.depositContract"); | ||
bytes32 internal constant PUBLIC_KEY_OWNERSHIP_SLOT = keccak256("StakingContract.publicKeyOwnership"); | ||
bytes32 internal constant WITHDRAWAL_CREDENTIALS_SLOT = keccak256("StakingContract.withdrawalCredentials"); | ||
|
||
uint256 public constant DEPOSIT_SIZE = 32 ether; | ||
uint256 public constant PUBLIC_KEY_LENGTH = 48; | ||
uint256 public constant SIGNATURE_LENGTH = 96; | ||
|
||
error Unauthorized(); | ||
error AlreadyInitialized(); | ||
error InvalidCall(); | ||
error InvalidArgument(); | ||
error InvalidValue(); | ||
error NotEnoughKeys(); | ||
|
||
event Deposit(address indexed caller, address indexed withdrawer, bytes publicKey, bytes32 publicKeyRoot); | ||
|
||
modifier init(uint256 _version) { | ||
if (_version != VERSION_SLOT.getUint256() + 1) { | ||
revert AlreadyInitialized(); | ||
} | ||
|
||
VERSION_SLOT.setUint256(_version); | ||
|
||
_; | ||
} | ||
|
||
modifier onlyAdmin() { | ||
if (msg.sender != ADMIN_SLOT.getAddress()) { | ||
revert Unauthorized(); | ||
} | ||
|
||
_; | ||
} | ||
|
||
function initialize_1( | ||
address _admin, | ||
uint256 _fee, | ||
address _depositContract, | ||
bytes32 _withdrawalCredentials | ||
) external init(1) { | ||
ADMIN_SLOT.setAddress(_admin); | ||
FEE_SLOT.setUint256(_fee); | ||
DEPOSIT_CONTRACT_SLOT.setAddress(_depositContract); | ||
WITHDRAWAL_CREDENTIALS_SLOT.setBytes32(_withdrawalCredentials); | ||
} | ||
|
||
function getValidatorCount() external view returns (uint256) { | ||
return VALIDATOR_COUNT_SLOT.getUint256(); | ||
} | ||
|
||
function getKeyCount() external view returns (uint256) { | ||
return PUBLIC_KEYS_SLOT.getStorageBytesArray().value.length; | ||
} | ||
|
||
function getWithdrawer(bytes memory _publicKey) external view returns (address) { | ||
bytes32 pubkeyRoot = sha256(BytesLib.pad64(_publicKey)); | ||
StateLib.Bytes32ToAddressMappingSlot storage publicKeyOwnership = PUBLIC_KEY_OWNERSHIP_SLOT | ||
.getStorageBytes32ToAddressMapping(); | ||
return publicKeyOwnership.value[pubkeyRoot]; | ||
} | ||
|
||
function getFee() external view returns (uint256) { | ||
return FEE_SLOT.getUint256(); | ||
} | ||
|
||
function deposit(address _withdrawer) external payable { | ||
_deposit(_withdrawer); | ||
} | ||
|
||
receive() external payable { | ||
_deposit(msg.sender); | ||
} | ||
|
||
fallback() external payable { | ||
revert InvalidCall(); | ||
} | ||
|
||
function registerValidatorKeys(bytes[] memory publicKeys, bytes[] memory signatures) external onlyAdmin { | ||
if (publicKeys.length != signatures.length || publicKeys.length == 0) { | ||
revert InvalidArgument(); | ||
} | ||
|
||
StateLib.BytesArraySlot storage publicKeysStore = PUBLIC_KEYS_SLOT.getStorageBytesArray(); | ||
StateLib.BytesArraySlot storage signaturesStore = SIGNATURES_SLOT.getStorageBytesArray(); | ||
|
||
for (uint256 i; i < publicKeys.length; ) { | ||
if (publicKeys[i].length != 48 || signatures[i].length != 96) { | ||
revert InvalidArgument(); | ||
} | ||
publicKeysStore.value.push(publicKeys[i]); | ||
signaturesStore.value.push(signatures[i]); | ||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
|
||
function setWithdrawer(bytes memory _publicKey, address _newWithdrawer) external { | ||
bytes32 pubkeyRoot = sha256(BytesLib.pad64(_publicKey)); | ||
StateLib.Bytes32ToAddressMappingSlot storage publicKeyOwnership = PUBLIC_KEY_OWNERSHIP_SLOT | ||
.getStorageBytes32ToAddressMapping(); | ||
|
||
if (msg.sender != publicKeyOwnership.value[pubkeyRoot]) { | ||
revert Unauthorized(); | ||
} | ||
|
||
publicKeyOwnership.value[pubkeyRoot] = _newWithdrawer; | ||
} | ||
|
||
function setFee(uint256 _newFee) external onlyAdmin { | ||
FEE_SLOT.setUint256(_newFee); | ||
} | ||
|
||
function _useKeys( | ||
bytes memory _publicKey, | ||
bytes memory _signature, | ||
bytes32 _withdrawalCredentials, | ||
address _withdrawer | ||
) internal { | ||
bytes32 pubkeyRoot = sha256(BytesLib.pad64(_publicKey)); | ||
bytes32 signatureRoot = sha256( | ||
abi.encodePacked( | ||
sha256(BytesLib.slice(_signature, 0, 64)), | ||
sha256(BytesLib.pad64(BytesLib.slice(_signature, 64, SIGNATURE_LENGTH - 64))) | ||
) | ||
); | ||
|
||
uint256 depositAmount = DEPOSIT_SIZE / 1000000000 wei; | ||
assert(depositAmount * 1000000000 wei == DEPOSIT_SIZE); | ||
|
||
bytes32 depositDataRoot = sha256( | ||
abi.encodePacked( | ||
sha256(abi.encodePacked(pubkeyRoot, _withdrawalCredentials)), | ||
sha256(abi.encodePacked(Uint256Lib.toLittleEndian64(depositAmount), signatureRoot)) | ||
) | ||
); | ||
|
||
uint256 targetBalance = address(this).balance - DEPOSIT_SIZE; | ||
|
||
IDepositContract(DEPOSIT_CONTRACT_SLOT.getAddress()).deposit{value: DEPOSIT_SIZE}( | ||
_publicKey, | ||
abi.encodePacked(_withdrawalCredentials), | ||
_signature, | ||
depositDataRoot | ||
); | ||
|
||
require(address(this).balance == targetBalance, "EXPECTING_DEPOSIT_TO_HAPPEN"); | ||
|
||
StateLib.Bytes32ToAddressMappingSlot storage publicKeyOwnership = PUBLIC_KEY_OWNERSHIP_SLOT | ||
.getStorageBytes32ToAddressMapping(); | ||
|
||
publicKeyOwnership.value[pubkeyRoot] = _withdrawer; | ||
|
||
emit Deposit(msg.sender, _withdrawer, _publicKey, pubkeyRoot); | ||
} | ||
|
||
function _deposit(address _withdrawer) internal { | ||
uint256 fee = FEE_SLOT.getUint256(); | ||
|
||
if (msg.value == 0 || msg.value % (DEPOSIT_SIZE + fee) != 0) { | ||
revert InvalidValue(); | ||
} | ||
|
||
uint256 depositCount = msg.value / (DEPOSIT_SIZE + fee); | ||
uint256 validatorCount = VALIDATOR_COUNT_SLOT.getUint256(); | ||
StateLib.BytesArraySlot storage publicKeysStore = PUBLIC_KEYS_SLOT.getStorageBytesArray(); | ||
StateLib.BytesArraySlot storage signaturesStore = SIGNATURES_SLOT.getStorageBytesArray(); | ||
bytes32 withdrawalCredentials = WITHDRAWAL_CREDENTIALS_SLOT.getBytes32(); | ||
|
||
if (validatorCount + depositCount > publicKeysStore.value.length) { | ||
revert NotEnoughKeys(); | ||
} | ||
|
||
for (uint256 i; i < depositCount; ) { | ||
_useKeys( | ||
publicKeysStore.value[validatorCount + i], | ||
signaturesStore.value[validatorCount + i], | ||
withdrawalCredentials, | ||
_withdrawer | ||
); | ||
unchecked { | ||
++i; | ||
} | ||
} | ||
|
||
VALIDATOR_COUNT_SLOT.setUint256(validatorCount + depositCount); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
pragma solidity >=0.8.10; | ||
|
||
interface IDepositContract { | ||
function deposit( | ||
bytes calldata pubkey, | ||
bytes calldata withdrawalCredentials, | ||
bytes calldata signature, | ||
bytes32 depositDataRoot | ||
) external payable; | ||
} |
Oops, something went wrong.