Last active
March 5, 2024 21:41
-
-
Save hm3736485/96ff835e4df4584437449da5fdc17759 to your computer and use it in GitHub Desktop.
OrbitBridgeIncidentTxVerification
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
// SPDX-License-Identifier: UNLICENSED | |
pragma solidity >=0.6.0 <0.9.0; | |
import {Test, console2} from "lib/forge-std/src/Test.sol"; | |
/// Attacker TX via orbit bridge API: https://bridge.orbitchain.io/open/v1/api/board/0x9263e7873613DDc598a701709875634819176AfF/1?address=0x9263e7873613DDc598a701709875634819176AfF | |
contract BlogTest is Test { | |
address public constant ETH_VAULT_PROXY_CONTRACT = 0x1Bf68A9d1EaEe7826b3593C20a0ca93293cb489a; | |
address public constant ATTACKER = 0x9263e7873613DDc598a701709875634819176AfF; | |
// Orbit hub contract on orbit mainnet : https://bridge-docs.orbitchain.io/contract/orbithub | |
address public constant HUB_CONTRACT = 0xB5680a55d627c52DE992e3EA52a86f19DA475399; | |
// "Orbit Bridge Exploiter 11", one of many EOA accounts where stolen funds were sent. | |
address public constant ETH_WALLET_ATTACKER = 0x27E2cc59a64D705a6c3D3d306186c2a55DcD5710; | |
/// seems to be another wallet used by the attacker where USDT was sent. | |
address public constant TETHER_WALLET_ATTACKER = 0xBD2830BA5357ec1EB49874E9Df14317229BA06D2; | |
address public constant TETHER_TOKEN_CONTRACT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; | |
/* validator addresses, taken from: | |
https://bridge-docs.orbitchain.io/official-governance | |
https://github.com/orbit-chain/bridge-launch-governance/ | |
https://bridge.orbitchain.io/open/v1/api/board/0x9263e7873613DDc598a701709875634819176AfF/1?address=0x9263e7873613DDc598a701709875634819176AfF | |
*/ | |
address public constant ORBIT_BRIDGE_HASHQUARK_GOVERNANCE_VALIDATOR = 0x6013f0B3ffE1fFdcA3Fc6A8cd705b1Af048F7437; | |
address public constant ORBIT_BRIDGE_DSRV_GOVERNANCE_VALIDATOR = 0x3924Ac70075078A7713f543b72e3F8817ecEc646; | |
address public constant ORBIT_BRIDGE_KLAYTN_GOVERNANCE_VALIDATOR = 0x34EBf4f74a881eB63F666E63ce1Ff2F287CA5a8b; | |
address public constant ORBIT_BRIDGE_EVERSTAKE_GOVERNANCE_VALIDATOR = 0xa6dc28CbcB2f8060a00b4FA67F9b67775AC5a3a1; | |
address public constant ORBIT_BRIDGE_AVALANCHE_GOVERNANCE_VALIDATOR = 0x7F4b332611818aE13c76f9222e2229f274Ded9BD; | |
address public constant ORBIT_BRIDGE_MOONRIVER_GOVERNANCE_VALIDATOR = 0x595f1a527527Fa28A8C5f294c49E51B9799fdAF0; | |
address public constant ORBIT_BRIDGE_OZYS_GOVERNANCE_VALIDATOR = 0x8a3F117Ef3b40f1661Dedf7f28fC33E7b6fae4F8; | |
address public constant ORBIT_BRIDGE_ICON_GOVERNANCE_VALIDATOR = 0x1c0Cd56F1c3E2cF13B9B44dBE5529104bade543E; | |
address public constant ORBIT_BRIDGE_ETH_GOVERNANCE_VALIDATOR = 0x3b6590Ff12Ba188e465395E1610D8368613054B0; | |
address public constant ORBIT_BRIDGE_DXM_GOVERNANCE_VALIDATOR = 0x67C3c784C49d9ab8757ADb71491df1A1B38FbFA8; | |
// bytes32s[0] arg re-used for all withdraw txs | |
// bytes32s [0]:govId, [1]:txHash | |
// txHash is transaction hash on orbit chain https://explorer.orbitchain.io/transactions/0xce133a5bf0a53be73935fa24abe31c509a912e3220c17afc67150f8b47a886a6 | |
bytes32 public constant GOV_ID = 0x50f408f4b0fb17bf4f5143de4bd95802410d00422f008e9deef06fb101a0f060; | |
string public constant chain = "ETH"; | |
IEthVault iEthVault; | |
IERC20 usdc; | |
IERC20 weth; | |
IERC20 wbtc; | |
IERC20 dai; | |
IERC20 tether; | |
IERC20 orcToken; | |
string infuraUrl = vm.envString("MAINNET"); | |
uint256 blockHeight = 18908122; //block height before 30M USDT theft on eth mainnet - 1 | |
uint256 mainnetFork = vm.createFork(infuraUrl); | |
function setUp() public { | |
// fork and set block height | |
vm.selectFork(mainnetFork); | |
vm.rollFork(blockHeight); | |
console2.log("Forking from ETH mainnet at height: %s" , blockHeight); | |
iEthVault = IEthVault(ETH_VAULT_PROXY_CONTRACT); | |
vm.label(ATTACKER, "Attacker"); | |
vm.label(ETH_VAULT_PROXY_CONTRACT, "ETH Vault Proxy"); | |
} | |
//30 Mil USDT stolen on ETH mainnet: https://etherscan.io/tx/0xd8ca42941a0a2c25669267ad8d61f7f9f4118252cb502316602fe16624b80ac8 | |
function test_USDT_validate() public { | |
string memory fromChain = "ORBIT"; | |
address fromAddr = ATTACKER; | |
address toAddr = 0xDAdfa3Ccd40Fc3d5A0164c6f9444f60163cCBf3b; | |
address token = TETHER_TOKEN_CONTRACT; | |
bytes32[] memory bytes32s = new bytes32[](2); | |
bytes32s[0] = GOV_ID; | |
bytes32s[1] = 0x8c799442963997a34dc0749f9d02433d3d79839fea19dfd6a3ca190b8a231a2b; | |
uint256[] memory uints = new uint256[](3); | |
uints[0] = 30000000000000; // 30 million USDT | |
uints[1] = 6; | |
uints[2] = 8729; | |
uint8[] memory v = new uint8[](15); | |
v[0] = 27; | |
v[1] = 27; | |
v[2] = 27; | |
v[3] = 28; | |
v[4] = 28; | |
v[5] = 28; | |
v[6] = 28; | |
v[7] = 27; | |
v[8] = 27; | |
v[9] = 27; | |
v[10] = 27; | |
v[11] = 27; | |
v[12] = 28; | |
v[13] = 27; | |
v[14] = 27; | |
bytes32[] memory r = new bytes32[](15); | |
r[0] = 0x7baf2dcc87b95c2cc498df55386e59f88b442a755346b0ce85523afdc189c014; | |
r[1] = 0xd6c2cb91e3c8b2a3c1c786a4597cd7a21f4be946c718c7bb67cb817ce856e1bd; | |
r[2] =0x60accec86324e6930525578b783ec0baaa4653db2493627e892933e767ed57dc; | |
r[3] =0xfbc3f634967093418e28f19def2ea7f1a924b37e8eacde859876253e7147f9f8; | |
r[4] =0x8d716a957cdb9546fc9bb45f38b6b75d11e130d9293782f8530053d05f56701b; | |
r[5] =0x3ed045d5b28e3d89b49da98b072580f4e4d91c0dc8d385996107e7c797bf1f3e; | |
r[6] =0x12797b354f0f691dfae7f9ebc971b58a8d9af5bfd632cacfae5ee3cdb6b3495e; | |
r[7] =0x596742ec19413368855e579ee22395a1ce0e79163321d0131eb65882b17563e9; | |
r[8] =0x5a65f067846e6d11d96e5861347349072c8083750e88ea5f7cbcb90a70d465ac; | |
r[9] =0xbd3d7b5909b382517fc61f927eefe06dda7ebde9ab8213defe2cbe241ce0cc37; | |
r[10] =0x66f3c23392ce395f2acb35f506333cce8c78d8f36a68eece8dfd5d49af0e8c2b; | |
r[11] =0xd040f048c55b0ce2999b2767ae44aa693069229cbd0dc9082d539852c08d1b6f; | |
r[12] =0x899db261fef30e64302c0e8858153d0a5db060637ad6aaee4d7c7adce19a332a; | |
r[13] =0xb104fa1e7978982a1294adc586f2f95dc21f13d0207bfe73922e5655d19dc71e; | |
r[14] =0x0ba2460bfbf4dab27f764ce9dfe2c68bdfcfd895cac1460c3c9b4103d30a7475; | |
bytes32[] memory s = new bytes32[](15); | |
s[0] =0x75caf0ae5e4722446c1829f6cf41f4958f4cef631fee8c5efc30613bbe44d5c1; | |
s[1] =0x2c7ee8df20074f16c51e93fa2ace8d809da2b0ca1e2e97471d4802cd5a370220; | |
s[2] =0x36097971dc91fd4ae0eafbad9b0f9cc6c5d7fe9334c04e6bd7538b75378d9d04; | |
s[3] =0x2216ca0d9413f1d05936bf6f47e39c63d54021644221bca66b126192f6d59297; | |
s[4] =0x4892247544ff45cd19cf8add312a296a48402d43909f5f01b7070404b4e30937; | |
s[5] =0x0d9264fbd050bc5654c19c695c802185799f570383b8e3c9fc1909fcd5c83eb9; | |
s[6] =0x118b1bad3a050cdc6c53a42c5161bf9ff719cfcf8c9d0335cd2fac58931abed9; | |
s[7] =0x69af95754cc75adb06f70b69fc092261d7931c0567606888851294753b7f1e70; | |
s[8] =0x28ec8db94c4ce5c82f21470a515111de74276e633b30c02d670359e9b9c4eb13; | |
s[9] =0x7ab97c30070d35d41ea41d7103e8a6f4c28478b5af1fa655640d81e96fbf70f9; | |
s[10] =0x4fd23d8aeb626281096d48caf5b32a3dee2b323047acecaa42fb7fb1b004c1db; | |
s[11] =0x66a628a5aab5049d6585009da7545188004d11d2847c1c57e2d0c384612b56f0; | |
s[12] =0x6167dfff0a2bd420d981f50ec8ebe0588c6c29a66ee18f4c81f154725f432385; | |
s[13] =0x53c918a35c619569ccd6ff484039ec9cd456ca2e653b4d922ec83acb1c430f6e; | |
s[14] =0x44b891c61d62913a9aaa1e19725e7a87b510b112cba8aeee48cd1e85ac867eb8; | |
bytes32 whash = sha256(abi.encodePacked(HUB_CONTRACT, fromChain, chain, fromAddr, toAddr, token, bytes32s, uints)); | |
uint validatorCount = 0; | |
address[] memory vaList = new address[](iEthVault.getOwners().length); | |
address[] memory knownValidators = new address[](10); | |
knownValidators[0] = ORBIT_BRIDGE_HASHQUARK_GOVERNANCE_VALIDATOR; | |
knownValidators[1] = ORBIT_BRIDGE_DSRV_GOVERNANCE_VALIDATOR; | |
knownValidators[2] = ORBIT_BRIDGE_KLAYTN_GOVERNANCE_VALIDATOR; | |
knownValidators[3] = ORBIT_BRIDGE_EVERSTAKE_GOVERNANCE_VALIDATOR; | |
knownValidators[4] = ORBIT_BRIDGE_AVALANCHE_GOVERNANCE_VALIDATOR; | |
knownValidators[5] = ORBIT_BRIDGE_MOONRIVER_GOVERNANCE_VALIDATOR; | |
knownValidators[6] = ORBIT_BRIDGE_OZYS_GOVERNANCE_VALIDATOR; | |
knownValidators[7] = ORBIT_BRIDGE_ICON_GOVERNANCE_VALIDATOR; | |
knownValidators[8] = ORBIT_BRIDGE_ETH_GOVERNANCE_VALIDATOR; | |
knownValidators[9] = ORBIT_BRIDGE_DXM_GOVERNANCE_VALIDATOR; | |
uint i=0; | |
uint j=0; | |
for(i; i<v.length; i++){ | |
address va = ecrecover(whash,v[i],r[i],s[i]); | |
if(iEthVault.isOwner(va)){ // confirming if the supplied signatures originate from EthVault's validator in isOwner mapping. | |
for(j=0; j<validatorCount; j++){ | |
require(vaList[j] != va); | |
require(knownValidators[j] != va); | |
} | |
vaList[validatorCount] = va; | |
knownValidators[validatorCount] = va; | |
validatorCount += 1; | |
} | |
} | |
assertGe(validatorCount, iEthVault.required()); // confirm that at least 7 validators signed the whash | |
assertEq(validatorCount, knownValidators.length); // confirm that 10 validators signed the transaction | |
} | |
/* | |
api: | |
http://localhost:18987/v1/gov/validate/0xaE0bDc4eEAC5E950B67C6819B118761CaAF61946/0x6552e7dc22a3356c4593095059941a5247270b74e3c1201b769c5d54624dafc1 | |
mig.validate call function args: | |
Method: a322da5a | |
------------ | |
[000]: 000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266 | |
[020]: 6552e7dc22a3356c4593095059941a5247270b74e3c1201b769c5d54624dafc1 | |
[040]: 000000000000000000000000000000000000000000000000000000000000001c | |
[060]: d7ff16e9b520d0744f40c3acd960c55700f0677738e5bd1041b9d5d8190b8871 | |
[080]: 5ef979ccf9d5347bec4a6cee36324dfa81557649505e4871c704a4b6d645e74b | |
*/ | |
function test_api_validate() public { | |
console2.log("validating sigs derived from validator API"); | |
// account used to sign sigHash in testing validator API | |
address apiSigner = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; | |
// supplied hash | |
bytes32 apiSigHash = 0x6552e7dc22a3356c4593095059941a5247270b74e3c1201b769c5d54624dafc1; | |
address fromAddr = ATTACKER; | |
address toAddr = ETH_WALLET_ATTACKER; | |
address token = address(0); | |
string memory fromChain = "ORBIT"; | |
bytes32[] memory bytes32s = new bytes32[](2); | |
bytes32s[0] = GOV_ID; | |
bytes32s[1] = 0xce133a5bf0a53be73935fa24abe31c509a912e3220c17afc67150f8b47a886a6; | |
uint256[] memory uints = new uint256[](3); | |
uints[0] = 200199600000000000; | |
uints[1] = 18; | |
uints[2] = 8646; | |
uint8[] memory v = new uint8[](1); | |
v[0] = 28; | |
bytes32[] memory r = new bytes32[](1); | |
r[0] = 0xd7ff16e9b520d0744f40c3acd960c55700f0677738e5bd1041b9d5d8190b8871; | |
bytes32[] memory s = new bytes32[](1); | |
s[0] = 0x5ef979ccf9d5347bec4a6cee36324dfa81557649505e4871c704a4b6d645e74b; | |
bytes32 whash = sha256(abi.encodePacked(HUB_CONTRACT, fromChain, chain, fromAddr, toAddr, token, bytes32s, uints)); | |
// confirm hash generated on chain matches hash supplied to API | |
assertEq(whash, apiSigHash); | |
address va = ecrecover(whash,v[0],r[0],s[0]); | |
// confirm api signer account signed hash generated on chain | |
assertEq(va, apiSigner); | |
} | |
} | |
// SPDX-License-Identifier: UNLICENSED | |
pragma solidity >=0.6.0 <0.9.0; | |
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol) | |
pragma solidity ^0.8.0; | |
/** | |
* @dev Interface of the ERC20 standard as defined in the EIP. | |
*/ | |
interface IERC20 { | |
/** | |
* @dev Returns the amount of tokens in existence. | |
*/ | |
function totalSupply() external view returns (uint256); | |
/** | |
* @dev Returns the amount of tokens owned by `account`. | |
*/ | |
function balanceOf(address account) external view returns (uint256); | |
/** | |
* @dev Moves `amount` tokens from the caller's account to `to`. | |
* | |
* Returns a boolean value indicating whether the operation succeeded. | |
* | |
* Emits a {Transfer} event. | |
*/ | |
function transfer(address to, uint256 amount) external returns (bool); | |
/** | |
* @dev Returns the remaining number of tokens that `spender` will be | |
* allowed to spend on behalf of `owner` through {transferFrom}. This is | |
* zero by default. | |
* | |
* This value changes when {approve} or {transferFrom} are called. | |
*/ | |
function allowance(address owner, address spender) | |
external | |
view | |
returns (uint256); | |
/** | |
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens. | |
* | |
* Returns a boolean value indicating whether the operation succeeded. | |
* | |
* IMPORTANT: Beware that changing an allowance with this method brings the risk | |
* that someone may use both the old and the new allowance by unfortunate | |
* transaction ordering. One possible solution to mitigate this race | |
* condition is to first reduce the spender's allowance to 0 and set the | |
* desired value afterwards: | |
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 | |
* | |
* Emits an {Approval} event. | |
*/ | |
function approve(address spender, uint256 amount) external returns (bool); | |
/** | |
* @dev Moves `amount` tokens from `from` to `to` using the | |
* allowance mechanism. `amount` is then deducted from the caller's | |
* allowance. | |
* | |
* Returns a boolean value indicating whether the operation succeeded. | |
* | |
* Emits a {Transfer} event. | |
*/ | |
function transferFrom( | |
address from, | |
address to, | |
uint256 amount | |
) external returns (bool); | |
/** | |
* @dev Emitted when `value` tokens are moved from one account (`from`) to | |
* another (`to`). | |
* | |
* Note that `value` may be zero. | |
*/ | |
event Transfer(address indexed from, address indexed to, uint256 value); | |
/** | |
* @dev Emitted when the allowance of a `spender` for an `owner` is set by | |
* a call to {approve}. `value` is the new allowance. | |
*/ | |
event Approval( | |
address indexed owner, | |
address indexed spender, | |
uint256 value | |
); | |
} | |
interface IEthVault { | |
struct Transaction { | |
address destination; | |
uint value; | |
bytes data; | |
bool executed; | |
} | |
function getVersion() external pure returns(string memory); | |
function isOwner(address addr) external returns(bool); | |
function farms(address addr) external returns(address payable); | |
function policyAdmin() external returns(address); | |
function transactions(uint _i) external returns (address dest, uint value, bytes memory data, bool executed); | |
function getOwners() external view returns(address[] memory); | |
function isUsedWithdrawal(bytes32 b) external returns(bool); | |
function confirmTransaction(uint transactionId) external; | |
function submitTransaction(address destination, uint value, bytes memory data) external returns (uint transactionId); | |
function getConfirmations(uint transactionId) external view returns (address[] memory _confirmations); | |
function changeActivate(bool activate) external; | |
function setTetherAddress(address tether) external; | |
function getChainId(string memory _chain) external view returns(bytes32); | |
function setValidChain(string memory _chain, bool valid) external; | |
function deposit(string memory toChain, bytes memory toAddr) payable external; | |
function depositToken(address token, string memory toChain, bytes memory toAddr, uint amount) external; | |
function addOwner(address owner) external; | |
function executeTransaction(uint transactionId) external; | |
function addTransaction(address destination, uint value, bytes memory data) external returns (uint transactionid); | |
function isConfirmed(uint transactionId) | |
external | |
view | |
returns (bool); | |
function replaceOwner(address owner, address newOwner) | |
external; | |
function required() external returns (uint); | |
function getTransactionCount(bool pending, bool executed) | |
external | |
view | |
returns (uint count); | |
function getTransactionIds(uint from, uint to, bool pending, bool executed) | |
external | |
view | |
returns (uint[] memory _transactionIds); | |
function governance_() external view returns (address); | |
// Fix Data Info | |
///@param bytes32s [0]:govId, [1]:txHash | |
///@param uints [0]:amount, [1]:decimals | |
function withdraw( | |
address hubContract, | |
string memory fromChain, | |
bytes memory fromAddr, | |
bytes memory toAddr, | |
bytes memory token, | |
bytes32[] memory bytes32s, | |
uint[] memory uints, | |
uint8[] memory v, | |
bytes32[] memory r, | |
bytes32[] memory s | |
) external; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment