Last active
July 12, 2023 17:15
-
-
Save jessiepathfinder/90757f284acd988f0a695222be8efaa5 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// SPDX-License-Identifier: AGPL-3.0-or-later | |
pragma solidity ^0.8.9; | |
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | |
import "./StorageProofs.sol"; | |
contract BridgeDrain{ | |
mapping(bytes32 => bytes32) public remoteAccountStateRoot; | |
mapping(address => uint256) public lastVotedBlockNumber; | |
mapping(uint256 => mapping(bytes32 => uint256)) private votesCount; | |
mapping(uint256 => uint256) private lastMostPopularChildChange; | |
mapping(uint256 => bytes32) private mostPopularChild; | |
bytes32 immutable initialHash; | |
uint256 immutable initialHeight; | |
address immutable remoteBridgeSource; | |
bytes32 immutable domain; | |
uint256 constant epouchSize = 256; | |
uint256 constant lastPopularChildFinalizationTime = 3 days; | |
function hash2(bytes32 blockHash, uint256 blockNum) internal view returns (bytes32){ | |
return keccak256(abi.encodePacked(blockHash, blockNum, domain)); | |
} | |
function checkSafeBlock(bytes32 blockHash, uint256 blockNum) public view returns (bool){ | |
if(blockHash == initialHash && blockNum == initialHeight){ | |
return true; | |
} | |
if(blockNum % epouchSize > 0 || blockNum < epouchSize){ | |
return false; | |
} | |
bytes32 mostPopularChild2 = mostPopularChild[blockNum - epouchSize]; | |
uint256 LPCC = lastMostPopularChildChange[blockNum - epouchSize]; | |
if(uint256(mostPopularChild2) == 0 || (block.timestamp - LPCC) < lastPopularChildFinalizationTime){ | |
return false; | |
} | |
return mostPopularChild2 == blockHash; | |
} | |
constructor(address _remoteBridgeSource, bytes32 _domain, bytes32 _initialHash, uint256 _initialHeight){ | |
remoteBridgeSource = _remoteBridgeSource; | |
domain = _domain; | |
initialHash = _initialHash; | |
initialHeight = _initialHeight; | |
} | |
function vote( | |
bytes32 prev, | |
bytes32 blockHash, | |
uint256 blockNumber, | |
bytes memory blockHeaderRLP, | |
bytes memory accountStateProof, | |
bytes memory storageProof, | |
uint8 v, bytes32 r, bytes32 s) | |
external{ | |
require(blockNumber > initialHeight, "can't vote against initial attestation"); | |
require(blockNumber % epouchSize == 0, "unaligned height"); | |
require(checkSafeBlock(prev, blockNumber - epouchSize), "previous hash unsafe"); | |
address validator = ecrecover(hash2(blockHash, blockNumber), v, r, s); | |
bytes32 accountRoot = remoteAccountStateRoot[prev]; | |
if(uint256(accountRoot) == 0){ | |
require(keccak256(blockHeaderRLP) == blockHash, "Incorrect block header"); | |
accountRoot = StorageOracle.getStorageRoot(remoteBridgeSource, blockHeaderRLP, accountStateProof); | |
remoteAccountStateRoot[prev] = accountRoot; | |
} | |
uint256 balance = uint256(StorageOracle.getStorage(accountRoot, uint256(keccak256(abi.encodePacked(uint256(uint160(validator)), uint256(0)))), storageProof)); | |
require(balance > 0, "your balance must be greater than 0"); | |
lastVotedBlockNumber[validator] = blockNumber; | |
bytes32 mostPopularChild1 = mostPopularChild[blockNumber - epouchSize]; | |
uint256 currentBlockVotes = votesCount[blockNumber - epouchSize][blockHash] + balance; | |
if(mostPopularChild1 != blockHash){ | |
uint256 LPCC = lastMostPopularChildChange[blockNumber - epouchSize]; | |
require(uint256(mostPopularChild1) == 0 || (block.timestamp - LPCC) < lastPopularChildFinalizationTime, "can't vote against safe block"); | |
if(currentBlockVotes > votesCount[blockNumber - epouchSize][mostPopularChild1]){ | |
mostPopularChild[blockNumber - epouchSize] = blockHash; | |
lastMostPopularChildChange[blockNumber - epouchSize] = block.timestamp; | |
} | |
} | |
votesCount[blockNumber - epouchSize][blockHash] = currentBlockVotes; | |
} | |
} | |
struct ExtendedValidatorInfo{ | |
uint8 status; //NOT_VALIDATOR, ACTIVE, EXIT, SLASHED | |
uint64 withdrawableTime; | |
} | |
contract BondManager{ | |
mapping(address => uint256) public validatorEffectiveBalance; | |
mapping(address => uint256) public validatorBalance2; | |
mapping(address => ExtendedValidatorInfo) public extendedValidatorInfo; | |
uint256 constant minValidatorStake = 1; | |
uint256 constant withdrawalDelay = 4 weeks; | |
bytes32 immutable domain; | |
IERC20 immutable stakingToken; | |
uint256 constant whistleblowerRewardX1000 = 10; | |
constructor(bytes32 _domain, IERC20 _stakingToken){ | |
domain = _domain; | |
stakingToken = _stakingToken; | |
} | |
function hash2(bytes32 blockHash, uint256 blockNum) internal view returns (bytes32){ | |
return keccak256(abi.encodePacked(blockHash, blockNum, domain)); | |
} | |
function proveFraud(bytes32 blockHash, uint256 blockNum, address rewardReceiver, uint8 v, bytes32 r, bytes32 s) external{ | |
bytes32 correctHash = blockhash(blockNum); | |
require(uint256(correctHash) > 0, "pre-constantinople lookback limit reached"); | |
if(blockNum < block.number){ | |
require(blockHash != correctHash, "correctly hashed"); | |
} | |
address validator = ecrecover(hash2(blockHash, blockNum), v, r, s); | |
ExtendedValidatorInfo memory validatorInfo = extendedValidatorInfo[validator]; | |
require(validatorInfo.status > 0, "not validator"); | |
require(validatorInfo.withdrawableTime > block.timestamp, "withdrawable"); | |
require(validatorInfo.status < 3, "validator already slashed"); | |
extendedValidatorInfo[validator] = ExtendedValidatorInfo(3, 0); | |
if(validatorInfo.status < 2){ | |
validatorEffectiveBalance[validator] = 0; | |
} | |
uint256 balance = validatorBalance2[validator]; | |
if(balance > 0){ | |
SafeERC20.safeTransfer(stakingToken, rewardReceiver, (balance * whistleblowerRewardX1000) / 1000); | |
} | |
} | |
function deposit(uint256 amount) external{ | |
require(amount >= minValidatorStake, "minimum stake required"); | |
ExtendedValidatorInfo memory validatorInfo = extendedValidatorInfo[msg.sender]; | |
require(validatorInfo.status < 2, "cannot top up exited validator"); | |
SafeERC20.safeTransferFrom(stakingToken, msg.sender, address(this), amount); | |
uint256 syncedAmt; | |
if(validatorInfo.status == 0){ | |
extendedValidatorInfo[msg.sender] = ExtendedValidatorInfo(1, 0); | |
syncedAmt = amount; | |
} else{ | |
syncedAmt = validatorBalance2[msg.sender] + amount; | |
} | |
validatorBalance2[msg.sender] = amount; | |
validatorEffectiveBalance[msg.sender] = amount; | |
} | |
function exit() external{ | |
ExtendedValidatorInfo memory validatorInfo = extendedValidatorInfo[msg.sender]; | |
require(validatorInfo.status == 1, "only active validators may exit"); | |
validatorEffectiveBalance[msg.sender] = 0; | |
extendedValidatorInfo[msg.sender] = ExtendedValidatorInfo(2, uint64(block.timestamp + withdrawalDelay)); | |
} | |
function withdraw(address to) external{ | |
ExtendedValidatorInfo memory validatorInfo = extendedValidatorInfo[msg.sender]; | |
require(validatorInfo.status == 1, "only exited validators may withdraw"); | |
require(validatorInfo.withdrawableTime < block.timestamp, "stake still locked"); | |
uint256 balance = validatorBalance2[msg.sender]; | |
//reset validator account status | |
validatorBalance2[msg.sender] = 0; | |
extendedValidatorInfo[msg.sender] = ExtendedValidatorInfo(0, 0); | |
SafeERC20.safeTransfer(stakingToken, to, balance); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment