Skip to content

Instantly share code, notes, and snippets.

@jessiepathfinder
Last active July 12, 2023 17:15
Show Gist options
  • Save jessiepathfinder/90757f284acd988f0a695222be8efaa5 to your computer and use it in GitHub Desktop.
Save jessiepathfinder/90757f284acd988f0a695222be8efaa5 to your computer and use it in GitHub Desktop.
// 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