Skip to content

Commit

Permalink
Merge pull request #149 from hansstammler/feat_issue_145
Browse files Browse the repository at this point in the history
feat #145: finished implementing monitors and exploits for cross-chai…
  • Loading branch information
mojtaba-eshghie authored Jun 25, 2024
2 parents a78e56e + 4620432 commit d2cb294
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 231 deletions.
117 changes: 65 additions & 52 deletions CI/envs/bridge-decentralized/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ const {
compileWithVersion,
deployContract
} = require('@lib/web3/deploy');
const Mutex = require('async-mutex').Mutex;
const Semaphore = require('async-mutex').Semaphore;
const withTimeout = require('async-mutex').withTimeout;

const projectRoot = path.resolve(__dirname, '..', '..', '..');
const contractsDir = path.join(projectRoot, 'contracts', 'src', 'cross-chain');
const { sleep } = require('@lib/os/process');


let tokenSource = fs.readFileSync(path.join(contractsDir, 'CrossToken.sol'), 'utf8');
let transactions = 0;
const mutex = new Mutex();
let lag;



Expand All @@ -33,7 +39,8 @@ const blockchains = new Map();
* @returns {Object} An object containing the sourceContract, tokenCrontract and destinationContract instances.
* @throws {Error} If there's an error during the deployment.
*/
async function deployBridge(web3, envInfo, name, nativeToken, router, vault, oracle, bridgeForwards, bridgeForwardsERC20){
async function deployBridge(web3, envInfo, name, nativeToken, router, vault, oracle, bridgeForwards, bridgeForwardsERC20, bridgeDelay = 0){
lag = bridgeDelay;
let routerSource = fs.readFileSync(path.join(contractsDir, router+'.sol'), 'utf8');
let vaultSource = fs.readFileSync(path.join(contractsDir, vault+'.sol'), 'utf8');
let oracleSource = fs.readFileSync(path.join(contractsDir, oracle+'.sol'), 'utf8');
Expand Down Expand Up @@ -84,8 +91,8 @@ async function deployBridge(web3, envInfo, name, nativeToken, router, vault, ora

//Add event listeners to relay transactions
routerContract.events.allEvents().on('data', (data) => {
console.log(JSON.stringify(data));
handleEvent(data, name);

});

return {
Expand Down Expand Up @@ -145,58 +152,64 @@ async function handleEvent(data, name){

//Handles all calls of the deposit function of the router contract, currently supports swap and add.
async function deposit(data, name){
let memo = parseMemo(data.returnValues.memo);

let target = blockchains.get(memo.chain);
try {
if(target){ //If blockchain exists
switch(memo.operation){
case "=":
case "SWAP":
let offset = transactions++;
let nonce = await target.web3.eth.getTransactionCount(target.signer, "pending") + offset;
//console.log(nonce);
//If 0x0 token, represent it as the name of the native token, eg. ETH or AVAX
let sourceAsset = data.returnValues.asset == "0x0000000000000000000000000000000000000000" ? name + '.' + blockchains.get(name).nativeToken : name + '.' + data.returnValues.asset;
//If native token, represent as 0x0
let targetToken = memo.asset == target.nativeToken ? "0x0000000000000000000000000000000000000000" : memo.asset;
//let amount = getExchange(sourceAsset, memo.asset, data.returnValues.amount);
let receipt;
if (targetToken == "0x0000000000000000000000000000000000000000"){
receipt = await target.bridgeForwards(memo.destaddr, targetToken, data.returnValues.amount, sourceAsset, "OUT:" + memo.destaddr).send({
from: target.signer,
gas: 300000,
nonce: nonce
});
}
else{
receipt = await target.bridgeForwardsERC20(memo.destaddr, targetToken, data.returnValues.amount, sourceAsset, memo.assetName, "OUT:" + memo.destaddr).send({
from: target.signer,
gas: 300000,
nonce: nonce
});
}
transactions--;
if(receipt.status){

let memo = parseMemo(data.returnValues.memo);
let target = blockchains.get(memo.chain);
const release = await mutex.acquire();
//await sleep(12000);
try {
if(target){ //If blockchain exists
switch(memo.operation){
case "=":
case "SWAP":
//console.log(nonce);
//If 0x0 token, represent it as the name of the native token, eg. ETH or AVAX
let sourceAsset = data.returnValues.asset == "0x0000000000000000000000000000000000000000" ? name + '.' + blockchains.get(name).nativeToken : name + '.' + data.returnValues.asset;
//If native token, represent as 0x0
let targetToken = memo.asset == target.nativeToken ? "0x0000000000000000000000000000000000000000" : memo.asset;
//let amount = getExchange(sourceAsset, memo.asset, data.returnValues.amount);
let receipt;
let expiry = Math.floor(Date.now() / 1000) + 10;
if(lag !=0){
await sleep(lag);
}
if (targetToken == "0x0000000000000000000000000000000000000000"){
receipt = await target.bridgeForwards(memo.destaddr, targetToken, data.returnValues.amount, sourceAsset, "OUT:" + memo.destaddr, expiry).send({
from: target.signer,
gas: 300000,
});
}
else{
receipt = await target.bridgeForwardsERC20(memo.destaddr, targetToken, data.returnValues.amount, sourceAsset, memo.assetName, "OUT:" + memo.destaddr, expiry).send({
from: target.signer,
gas: 300000,
});
}
if(receipt.status){
return;
}
break;
case "ADD": //Liquidity was added to the vault, no relaying necessary
return;
}
break;
case "ADD": //Liquidity was added to the vault, no relaying necessary
return;
default:
break;
default:
break;
}
}
} catch (error) {
console.log(error);
console.log("Could not relay transaction");
//If above section did not return, refund the transaction, could implement some kind of fee to stop spamming
/* console.log("Issuing refund");
target = blockchains.get(name);
let receipt = await target.vault.methods.bridgeForwards(data.returnValues.from, data.returnValues.asset, data.returnValues.amount, "REFUND:" + data.returnValues.from).send({
from: target.signer,
gas: 300000,
}); */
} finally {
release();
}
} catch (error) {
console.log(error);
console.log("Could not relay transaction");
}
//If above section did not return, refund the transaction, could implement some kind of fee to stop spamming
/* target = blockchains.get(name);
let receipt = await target.vault.methods.bridgeForwards(data.returnValues.from, data.returnValues.asset, data.returnValues.amount, "REFUND:" + data.returnValues.from).send({
from: target.signer,
gas: 300000,
}); */


}

module.exports = deployBridge;
2 changes: 1 addition & 1 deletion CI/setup-cross-chain-unified.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const bridgeTestLogger = getLogger('bridgetest');
// Manually configured two cross-chain exploits for the tool paper;
const exploitsList = [
'tests/Bridge.exploit1.js',
// 'tests/Bridge.exploit2.js',
//'tests/Bridge.exploit2.js',
];

async function setupAndRunTests() {
Expand Down
1 change: 0 additions & 1 deletion CI/tests/Bridge.exploit1.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ async function startUp() {

let routerSourceA = fs.readFileSync(path.join('contracts', 'src', 'cross-chain', 'EthRouterVulnerability1.sol'), 'utf8');
let routerABIA = await getContractABI(routerSourceA, 'EthRouterVulnerability1', 'EthRouterVulnerability1');
console.log(routerABIA);

let vaultSourceA = fs.readFileSync(path.join('contracts', 'src', 'cross-chain', 'EthVaultOracle.sol'), 'utf8');
let vaultABIA = await getContractABI(vaultSourceA, 'EthVaultOracle', 'EthVaultOracle');
Expand Down
86 changes: 71 additions & 15 deletions CI/tests/Bridge.exploit2.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ const chalk = require('chalk');
const {
extractSolcVersion,
compileWithVersion,
deployContract
deployContract,
getContractABI,
} = require('@lib/web3/deploy');
const setupAvalancheEnv = require('@envs/avalanche-subnet');
const setupAnvilEnv = require('@envs/anvil');
const deployBridge = require('@envs/bridge-decentralized');
const { sleep } = require('@lib/os/process');
const { getTime } = require('date-fns');

const Monitor = require('@monitor/multi-chain-monitor');
const getLogger = require('@lib/logging/logger').getLogger;
const bridgeTestLogger = getLogger('bridgetest');
const { getActivities } = require('@lib/dcr/info');

async function sequence(contractsA, contractsB, web3A, web3B){

Expand Down Expand Up @@ -82,7 +83,7 @@ async function sequence(contractsA, contractsB, web3A, web3B){
let balance = await web3B.eth.getBalance(vaultB._address);
console.log(balance);

let expiry = Math.floor(Date.now() / 1000) - 100;
let expiry = Math.floor(Date.now() / 1000) + 100;
console.log(expiry);

let receipt1 = await routerA.methods.eth_depositWithExpiry(vaultA._address, "0x0000000000000000000000000000000000000000", 0, "SWAP:B.AVAX:" + accountB, expiry).send({
Expand Down Expand Up @@ -144,17 +145,72 @@ async function startUp() {
bridgeTestLogger.debug("Web3 A: " + envAnvil.envInfo.rpcAddress);
bridgeTestLogger.debug("Web3 B: " + envAvalanche.envInfo.rpcAddress);

let contractsA = await deployBridge(envAnvil.web3, envAnvil.envInfo, 'A', 'ETH', 'EthRouterVulnerability2', 'EthVaultOracle', 'Oracle', 'eth_bridgeForwards', 'eth_bridgeForwardsERC20');
let contractsB = await deployBridge(envAvalanche.web3, envAvalanche.envInfo, 'B', 'AVAX', 'AvaxRouter', 'AvaxVaultOracle', 'Oracle', 'avax_bridgeForwards', 'avax_bridgeForwardsERC20');

let contractSourceA = fs.readFileSync(path.join('contracts', 'src', 'cross-chain', 'EthRouter.sol'), 'utf8');

let contractSourceB = fs.readFileSync(path.join('contracts', 'src', 'cross-chain', 'AvaxRouter.sol'), 'utf8');

bridgeTestLogger.debug("Executing sequence");
let execution = await sequence(contractsA, contractsB, envAnvil.web3, envAvalanche.web3);
bridgeTestLogger.debug("Done");

let contractsA = await deployBridge(envAnvil.web3, envAnvil.envInfo, 'A', 'ETH', 'EthRouter', 'EthVaultOracle', 'Oracle', 'eth_bridgeForwards', 'eth_bridgeForwardsERC20', 11000);
let contractsB = await deployBridge(envAvalanche.web3, envAvalanche.envInfo, 'B', 'AVAX', 'AvaxRouter', 'AvaxVaultOracleVulnerability2', 'Oracle', 'avax_bridgeForwards', 'avax_bridgeForwardsERC20', 11000);

let routerSourceA = fs.readFileSync(path.join('contracts', 'src', 'cross-chain', 'EthRouter.sol'), 'utf8');
let routerABIA = await getContractABI(routerSourceA, 'EthRouter', 'EthRouter');

let vaultSourceA = fs.readFileSync(path.join('contracts', 'src', 'cross-chain', 'EthVaultOracle.sol'), 'utf8');
let vaultABIA = await getContractABI(vaultSourceA, 'EthVaultOracle', 'EthVaultOracle');

let routerSourceB = fs.readFileSync(path.join('contracts', 'src', 'cross-chain', 'AvaxRouter.sol'), 'utf8');
let routerABIB = await getContractABI(routerSourceB, 'AvaxRouter', 'AvaxRouter');

let vaultSourceB = fs.readFileSync(path.join('contracts', 'src', 'cross-chain', 'AvaxVaultOracleVulnerability2.sol'), 'utf8');
let vaultABIB = await getContractABI(vaultSourceB, 'AvaxVaultOracleVulnerability2', 'AvaxVaultOracleVulnerability2');

// Let's start the monitor here
let configs = {
"contracts": [{
web3: envAnvil.web3,
contractAddress: contractsA.vault._address,
contractFileName: 'EthVaultOracle',
contractName: 'EthVaultOracle',
contractABI: vaultABIA,
modelFunctionParams: null,
},
{
web3: envAnvil.web3,
contractAddress: contractsA.router._address,
contractFileName: 'EthRouterVulnerability1',
contractName: 'EthRouterVulnerability1',
contractABI: routerABIA,
modelFunctionParams: null,
},
{
web3: envAvalanche.web3,
contractAddress: contractsB.vault._address,
contractFileName: 'AvaxVaultOracle',
contractName: 'AvaxVaultOracle',
contractABI: vaultABIB,
modelFunctionParams: null,
},
{
web3: envAvalanche.web3,
contractAddress: contractsB.router._address,
contractFileName: 'AvaxRouter',
contractName: 'AvaxRouter',
contractABI: routerABIB,
modelFunctionParams: null,
}],
activities: await getActivities(1823976),
modelId: 1823976,
hasResponseRelation: true,
modelName: "CrossChainDEX",
};
let monitor = new Monitor(configs);

monitor.on('statusChange', async (newStatus) => {
if (newStatus === 'INITIALIZED') {
bridgeTestLogger.debug(`Monitor is initialized...`);
monitor.start();
} else if (newStatus == 'RUNNING') {
bridgeTestLogger.debug("Executing sequence");
let execution = await sequence(contractsA, contractsB, envAnvil.web3, envAvalanche.web3);
bridgeTestLogger.debug("Done");
}
});
}
//startUp();

Expand Down
8 changes: 6 additions & 2 deletions contracts/src/cross-chain/AvaxVaultOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ contract AvaxVaultOracle {
address asset,
uint amountPaid,
string memory sourceAsset,
string memory memo
string memory memo,
uint expiration
) external onlyOwner {
require(block.timestamp < expiration);
uint amount = (oracleContract.getPrice(sourceAsset) * amountPaid)/1000;
require(
address(this).balance >= amount,
Expand All @@ -64,8 +66,10 @@ contract AvaxVaultOracle {
uint amountPaid,
string memory sourceAsset,
string memory targetAsset,//has format AVAX.0x12341...
string memory memo
string memory memo,
uint expiration
) external onlyOwner {
require(block.timestamp < expiration);
uint amount = oracleContract.getExchangeRate(amountPaid, sourceAsset, targetAsset);
routerContract.avax_payOut(to, asset, amount, memo);
}
Expand Down
76 changes: 76 additions & 0 deletions contracts/src/cross-chain/AvaxVaultOracleVulnerability2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
pragma solidity ^0.8.20;

interface AvaxRouter {
function avax_payOut(
address payable to,
address asset,
uint amount,
string memory memo
) external payable;
}

interface Oracle {
function getExchangeRate(uint amount, string memory sourceAsset, string memory targetAsset) external view returns (uint);
function getPrice(string memory asset) external view returns (uint256);
}

contract AvaxVaultOracleVulnerability2 {
address owner;
AvaxRouter routerContract;
Oracle oracleContract;

constructor(address _routerContract, address _oracleContract) {
owner = msg.sender;
routerContract = AvaxRouter(_routerContract);
oracleContract = Oracle(_oracleContract);
}

modifier onlyOwner() {
require(msg.sender == owner, "Only the owner of this contract can call this function");
_;
}

//Receive funds when msg.data is empty
receive() external payable {}

//Receive funds when msg.data is not empty
fallback() external payable {}

function fund() external payable {}

function avax_bridgeForwards(
address payable to,
address asset,
uint amountPaid,
string memory sourceAsset,
string memory memo,
uint expiration
) external onlyOwner {
require(block.timestamp > expiration);
uint amount = (oracleContract.getPrice(sourceAsset) * amountPaid)/1000;
require(
address(this).balance >= amount,
"Vault has insufficient funds"
);
routerContract.avax_payOut{value: amount}(
to,
asset,
amount,
memo
);
}

function avax_bridgeForwardsERC20(
address payable to,
address asset, //actual address of asset
uint amountPaid,
string memory sourceAsset,
string memory targetAsset,//has format AVAX.0x12341...
string memory memo,
uint expiration
) external onlyOwner {
require(block.timestamp < expiration);
uint amount = oracleContract.getExchangeRate(amountPaid, sourceAsset, targetAsset);
routerContract.avax_payOut(to, asset, amount, memo);
}
}
Loading

0 comments on commit d2cb294

Please sign in to comment.