Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solidity Bridge Deployment Scripts #681

Merged
14 commits merged into from
Nov 19, 2024

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// To Deploy
// forge script AtomicBridgeCounterpartyMOVEDeployer --fork-url https://holesky.infura.io/v3/YOUR_INFURA_PROJECT_ID --broadcast --verify --etherscan-api-key YOUR_ETHERSCAN_API_KEY
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import "forge-std/Script.sol";
import {AtomicBridgeCounterpartyMOVE} from "../src/AtomicBridgeCounterpartyMOVE.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract AtomicBridgeCounterpartyMOVEDeployer is Script {
TransparentUpgradeableProxy public atomicBridgeCounterpartyProxy;
TimelockController public timelock;
string public atomicBridgeCounterpartySignature = "initialize(address,address,uint256)";
address public proxyAdmin; // TODO: this has to be specified for upgrades

address public atomicBridgeInitiatorAddress = address(0x5FbDB2315678afecb367f032d93F642f64180aa3);
address public ownerAddress = address(0x5b97cdf756f6363A88706c376464180E008Bd88b);
uint256 public timeLockDuration = 1 days; // 24 hours in seconds (half that of the initiators)
uint256 public minDelay = 2 days; // 2-day delay for governance timelock

address public movementLabsSafe = address(0x493516F6dB02c9b7f649E650c5de244646022Aa0);
address public movementFoundationSafe = address(0x00db70A9e12537495C359581b7b3Bc3a69379A00);

bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

function run() external {
uint256 signer = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(signer);

address[] memory proposers = new address[](1);
address[] memory executors = new address[](1);

proposers[0] = movementLabsSafe;
executors[0] = movementFoundationSafe;

// Deploy TimelockController
timelock = new TimelockController(minDelay, proposers, executors, address(0));
console.log("Timelock deployed at:", address(timelock));

// Deploy AtomicBridgeCounterpartyMOVE contract
_deployAtomicBridgeCounterparty();

vm.stopBroadcast();
}

function _deployAtomicBridgeCounterparty() internal {
console.log("AtomicBridgeCounterpartyMOVE: deploying");

// Instantiate the implementation contract
AtomicBridgeCounterpartyMOVE atomicBridgeCounterpartyImplementation = new AtomicBridgeCounterpartyMOVE();

// Deploy the TransparentUpgradeableProxy
atomicBridgeCounterpartyProxy = new TransparentUpgradeableProxy(
address(atomicBridgeCounterpartyImplementation),
address(timelock), // Admin is the timelock
abi.encodeWithSignature(
atomicBridgeCounterpartySignature,
atomicBridgeInitiatorAddress, // AtomicBridgeInitiatorMOVE address
ownerAddress, // Owner of the contract
timeLockDuration // Timelock duration (48 hours)
)
);

console.log("AtomicBridgeCounterpartyMOVE deployed at proxy address:", address(atomicBridgeCounterpartyProxy));
console.log("Implementation address:", address(atomicBridgeCounterpartyImplementation));
}

function _upgradeAtomicBridgeCounterparty() internal {
console.log("AtomicBridgeCounterpartyMOVE: upgrading");
AtomicBridgeCounterpartyMOVE newCounterpartyImplementation = new AtomicBridgeCounterpartyMOVE();

timelock.schedule(
proxyAdmin,
0,
abi.encodeWithSignature(
"upgradeAndCall(address,address,bytes)",
address(atomicBridgeCounterpartyProxy),
address(newCounterpartyImplementation),
abi.encodeWithSignature(
atomicBridgeCounterpartySignature,
atomicBridgeInitiatorAddress,
ownerAddress,
timeLockDuration
)
),
bytes32(0),
bytes32(0),
block.timestamp + minDelay
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// To Deploy
// forge script AtomicBridgeInitiatorMOVEDeployer --fork-url https://holesky.infura.io/v3/YOUR_INFURA_PROJECT_ID --broadcast --verify --etherscan-api-key YOUR_ETHERSCAN_API_KEY
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import "forge-std/Script.sol";
import {AtomicBridgeInitiatorMOVE} from "../src/AtomicBridgeInitiatorMOVE.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract AtomicBridgeInitiatorMOVEDeployer is Script {
TransparentUpgradeableProxy public atomicBridgeProxy;
TimelockController public timelock;
string public atomicBridgeSignature = "initialize(address,address,uint256,uint256)";
address public proxyAdmin; // TODO: this has to be specified for upgrades

// Parameters
address public moveTokenAddress = address(0xC36ba8B8fD9EcbF36288b9B9B0ae9FC3E0645227);
address public ownerAddress = address(0x5b97cdf756f6363A88706c376464180E008Bd88b);
uint256 public timeLockDuration = 2 days; // 48 hours in seconds
uint256 public minDelay = 2 days; // 2-day delay for governance timelock

// Safe addresses (replace these with actual safe addresses)
address public movementLabsSafe = address(0x493516F6dB02c9b7f649E650c5de244646022Aa0);
address public movementFoundationSafe = address(0x00db70A9e12537495C359581b7b3Bc3a69379A00);

bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

function run() external {
uint256 signer = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(signer);

address[] memory proposers = new address[](1);
address[] memory executors = new address[](1);

proposers[0] = movementLabsSafe;
executors[0] = movementFoundationSafe;

// Deploy TimelockController
timelock = new TimelockController(minDelay, proposers, executors, address(0));
console.log("Timelock deployed at:", address(timelock));

// Deploy AtomicBridgeInitiatorMOVE contract
_deployAtomicBridge();

vm.stopBroadcast();
}

function _deployAtomicBridge() internal {
console.log("AtomicBridgeInitiatorMOVE: deploying");

// Instantiate the implementation contract
AtomicBridgeInitiatorMOVE atomicBridgeImplementation = new AtomicBridgeInitiatorMOVE();

// Deploy the TransparentUpgradeableProxy
atomicBridgeProxy = new TransparentUpgradeableProxy(
address(atomicBridgeImplementation),
address(timelock), // Admin is the timelock
abi.encodeWithSignature(
atomicBridgeSignature,
moveTokenAddress, // MOVE token address
ownerAddress, // Owner of the contract
timeLockDuration // Timelock duration (48 hours)
)
);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because initiator and counterparty are paired, I would suggest merging them and calling

AtomicBridgeInitiatorMOVE(atomicBridgeInitiatoryProxy).setAtomicBridgeCounterparty(address(atomicBridgeCounterpartyProxy));
AtomicBridgeCounterpartyMOVE(atomicBridgeCounterpartyProxy).setAtomicBridgeInitiator(address(atomicBridgeInitiatorProxy));

console.log("AtomicBridgeInitiatorMOVE deployed at proxy address:", address(atomicBridgeProxy));
console.log("Implementation address:", address(atomicBridgeImplementation));
}

function _upgradeAtomicBridge() internal {
console.log("AtomicBridgeInitiatorMOVE: upgrading");
AtomicBridgeInitiatorMOVE newBridgeImplementation = new AtomicBridgeInitiatorMOVE();

timelock.schedule(
proxyAdmin,
0,
abi.encodeWithSignature(
"upgradeAndCall(address,address,bytes)",
address(atomicBridgeProxy),
address(newBridgeImplementation),
abi.encodeWithSignature(
atomicBridgeSignature,
moveTokenAddress,
ownerAddress,
timeLockDuration
)
),
bytes32(0),
bytes32(0),
block.timestamp + minDelay
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,7 @@ contract AtomicBridgeCounterparty is IAtomicBridgeCounterparty, OwnableUpgradeab
address recipient,
uint256 amount
) external onlyOwner returns (bool) {
if (amount == 0) revert ZeroAmount();

if (atomicBridgeInitiator.poolBalance() < amount) revert InsufficientWethBalance();

// potentially mint some gas here for the recipient here. The recipient could be an account with gas already.

if (amount == 0) revert ZeroAmount();
// The time lock is now based on the configurable duration
uint256 timeLock = block.timestamp + counterpartyTimeLockDuration;

Expand Down
Copy link
Contributor

@andygolay andygolay Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xPrimata does there need to be some kind of assertion in lockBridgeTransfer, about available MOVE?

Or, if not, how can we be sure that the amount will be available to withdraw in completeBridgeTransfer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it has been bridged before. The poolBalance doesn't help at all with knowing that the amount will be available to withdraw in the case of an exploit.

I would suggest taking a look at ERC20 _transfer method and seeing that it's doing exactly what we do with poolBalance but with extra security steps.

Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ contract AtomicBridgeCounterpartyMOVE is IAtomicBridgeCounterpartyMOVE, OwnableU
// Configurable time lock duration
uint256 public counterpartyTimeLockDuration;

function initialize(address _atomicBridgeInitiator, address owner, uint256 _timeLockDuration) public initializer {
if (_atomicBridgeInitiator == address(0)) revert ZeroAddress();
// Prevents initialization of implementation contract exploits
constructor(){_disableInitializers();}

function initialize(address _atomicBridgeInitiator, address _owner, uint256 _timeLockDuration) public initializer {
if (_atomicBridgeInitiator == address(0) && _owner == address(0)) revert ZeroAddress();
if (_timeLockDuration == 0) revert ZeroValue();
atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(_atomicBridgeInitiator);
__Ownable_init(owner);
__Ownable_init(_owner);

// Set the configurable time lock duration
counterpartyTimeLockDuration = _timeLockDuration;
Expand All @@ -53,8 +57,6 @@ contract AtomicBridgeCounterpartyMOVE is IAtomicBridgeCounterpartyMOVE, OwnableU
uint256 amount
) external onlyOwner returns (bool) {
if (amount == 0) revert ZeroAmount();
if (atomicBridgeInitiatorMOVE.poolBalance() < amount) revert InsufficientMOVEBalance();

// The time lock is now based on the configurable duration
uint256 timeLock = block.timestamp + counterpartyTimeLockDuration;

Expand Down
21 changes: 4 additions & 17 deletions protocol-units/bridge/contracts/src/AtomicBridgeInitiator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ contract AtomicBridgeInitiator is IAtomicBridgeInitiator, OwnableUpgradeable {
}

struct BridgeTransfer {
uint256 amount;
address originator;
bytes32 recipient;
uint256 amount;
bytes32 hashLock;
uint256 timeLock; // in blocks
MessageState state;
Expand All @@ -23,9 +23,6 @@ contract AtomicBridgeInitiator is IAtomicBridgeInitiator, OwnableUpgradeable {
// Mapping of bridge transfer ids to BridgeTransfer structs
mapping(bytes32 => BridgeTransfer) public bridgeTransfers;

// Total WETH pool balance
uint256 public poolBalance;

address public counterpartyAddress;
IWETH9 public weth;
uint256 private nonce;
Expand All @@ -34,18 +31,15 @@ contract AtomicBridgeInitiator is IAtomicBridgeInitiator, OwnableUpgradeable {
uint256 public initiatorTimeLockDuration;

// Initialize the contract with WETH address, owner, custom time lock duration, and initial pool balance
function initialize(address _weth, address owner, uint256 _timeLockDuration, uint256 _initialPoolBalance) public initializer {
if (_weth == address(0)) {
function initialize(address _weth, address owner, uint256 _timeLockDuration) public initializer {
if (_weth == address(0) && owner == address(0)) {
revert ZeroAddress();
}
weth = IWETH9(_weth);
__Ownable_init(owner);

// Set the custom time lock duration
initiatorTimeLockDuration = _timeLockDuration;

// Set the initial pool balance
poolBalance = _initialPoolBalance;
}

function setCounterpartyAddress(address _counterpartyAddress) external onlyOwner {
Expand Down Expand Up @@ -75,16 +69,13 @@ contract AtomicBridgeInitiator is IAtomicBridgeInitiator, OwnableUpgradeable {
if (!weth.transferFrom(originator, address(this), wethAmount)) revert WETHTransferFailed();
}

// Update the pool balance
poolBalance += totalAmount;

// Generate a unique nonce to prevent replay attacks, and generate a transfer ID
bridgeTransferId = keccak256(abi.encodePacked(originator, recipient, hashLock, initiatorTimeLockDuration, block.timestamp, nonce++));

bridgeTransfers[bridgeTransferId] = BridgeTransfer({
amount: totalAmount,
originator: originator,
recipient: recipient,
amount: totalAmount,
hashLock: hashLock,
timeLock: block.timestamp + initiatorTimeLockDuration,
state: MessageState.INITIALIZED
Expand All @@ -110,8 +101,6 @@ contract AtomicBridgeInitiator is IAtomicBridgeInitiator, OwnableUpgradeable {
if (block.timestamp < bridgeTransfer.timeLock) revert TimeLockNotExpired();
bridgeTransfer.state = MessageState.REFUNDED;

// Decrease pool balance and transfer WETH back to originator
poolBalance -= bridgeTransfer.amount;
if (!weth.transfer(bridgeTransfer.originator, bridgeTransfer.amount)) revert WETHTransferFailed();

emit BridgeTransferRefunded(bridgeTransferId);
Expand All @@ -120,8 +109,6 @@ contract AtomicBridgeInitiator is IAtomicBridgeInitiator, OwnableUpgradeable {
// Counterparty contract to withdraw WETH for originator
function withdrawWETH(address recipient, uint256 amount) external {
if (msg.sender != counterpartyAddress) revert Unauthorized();
if (poolBalance < amount) revert InsufficientWethBalance();
poolBalance -= amount;
if (!weth.transfer(recipient, amount)) revert WETHTransferFailed();
}
}
Expand Down
Loading