Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f621f30
fix: remove gas airdrop logic
ChiTimesChi Apr 17, 2025
3b7545d
feat: allow governance to withdraw unspent gas airdops
ChiTimesChi Apr 17, 2025
2cde3ea
feat: add legacy flag
ChiTimesChi Apr 17, 2025
20b104d
test: coverage for legacy flagf
ChiTimesChi Apr 17, 2025
285d915
test: coverage for new gov actions
ChiTimesChi Apr 17, 2025
f778571
test: reenabling
ChiTimesChi Apr 17, 2025
e9bb701
chore: revert docs formatting changes
ChiTimesChi Apr 22, 2025
189b72e
chore: bump bridge version
ChiTimesChi Apr 23, 2025
acbcaa8
refactor: more explicit name for sending tokens
ChiTimesChi Apr 23, 2025
0f34b97
chore: update actions/cache@v2 to v4 in GitHub workflows
ChiTimesChi Apr 23, 2025
3407298
fix: update tests
ChiTimesChi Apr 23, 2025
84491d9
test: expected behaviour for mint/withdraw pre/post legacy workflows
ChiTimesChi May 30, 2025
2d66d69
feat: fallbacks to regular mint/withdraw
ChiTimesChi May 30, 2025
c314e1d
test: reentrancy cases
ChiTimesChi May 30, 2025
f9cda06
test: node group caller only
ChiTimesChi May 30, 2025
880b817
test: withdrawFees cases
ChiTimesChi May 30, 2025
82828e5
fix: reset fees before transfer
ChiTimesChi May 30, 2025
85d9566
fix: old repo tests (#338)
ChiTimesChi Jun 4, 2025
bd94918
Merge branch 'feat/syn-bridge-unified' into syn-100-legacy-workflows
ChiTimesChi Jun 4, 2025
f63c26d
refactor: better separation of security checks
ChiTimesChi Jun 4, 2025
ec6948b
Merge pull request #335 from synapsecns/syn-100-legacy-workflows
ChiTimesChi Jun 5, 2025
4616c2f
Merge branch 'feat/syn-bridge-unified' into syn-102-withdraw-fees
ChiTimesChi Jun 5, 2025
2b5e00a
Merge pull request #336 from synapsecns/syn-102-withdraw-fees
ChiTimesChi Jun 5, 2025
c35907c
SYN-106: minor suggestions (#337)
ChiTimesChi Jun 6, 2025
18b7fe9
Merge branch 'master' into feat/syn-bridge-unified
ChiTimesChi Jun 18, 2025
b7fb369
SYN-94: disable initializer in SynapseBridge implementation (#342)
ChiTimesChi Jun 27, 2025
a67c932
SYN-94: deploy script (#343)
ChiTimesChi Jun 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 73 additions & 18 deletions contracts/bridge/SynapseBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,23 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
bytes32 public constant NODEGROUP_ROLE = keccak256("NODEGROUP_ROLE");
bytes32 public constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");

uint256 public constant bridgeVersion = 8;
uint256 public constant chainGasAmount = 0;

mapping(address => uint256) private fees;

uint256 public startBlockNumber;
uint256 public constant bridgeVersion = 6;
uint256 public chainGasAmount;
/// @dev This is a variable taking the storage slot of deprecated chainGasAmount to prevent storage gap
uint256 private _deprecatedChainGasAmount;
address payable public WETH_ADDRESS;

mapping(bytes32 => bool) private kappaMap;
bool public isLegacySendDisabled;

modifier legacySendEnabled() {
require(!isLegacySendDisabled, "Legacy send is disabled");
_;
}

receive() external payable {}

Expand All @@ -44,8 +53,20 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
}

function setChainGasAmount(uint256 amount) external {
revert("Gas airdrop is disabled");
}

function withdrawChainGas() external {
require(hasRole(GOVERNANCE_ROLE, msg.sender), "Not governance");
chainGasAmount = amount;
emit ChainGasWithdrawn(msg.sender, address(this).balance);
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "ETH_TRANSFER_FAILED");
}

function setLegacySendDisabled(bool _isLegacySendDisabled) external {
require(hasRole(GOVERNANCE_ROLE, msg.sender), "Not governance");
isLegacySendDisabled = _isLegacySendDisabled;
emit LegacySendDisabledSet(_isLegacySendDisabled);
}

function setWethAddress(address payable _wethAddress) external {
Expand Down Expand Up @@ -120,6 +141,10 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
// v2 events
event TokenRedeemV2(bytes32 indexed to, uint256 chainId, IERC20 token, uint256 amount);

// New governance events
event LegacySendDisabledSet(bool isDisabled);
event ChainGasWithdrawn(address to, uint256 amount);

// VIEW FUNCTIONS ***/
function getFeeBalance(address tokenAddress) external view returns (uint256) {
return fees[tokenAddress];
Expand All @@ -138,9 +163,10 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
function withdrawFees(IERC20 token, address to) external whenNotPaused {
require(hasRole(GOVERNANCE_ROLE, msg.sender), "Not governance");
require(to != address(0), "Address is 0x000");
if (fees[address(token)] != 0) {
token.safeTransfer(to, fees[address(token)]);
uint256 amount = fees[address(token)];
if (amount != 0) {
fees[address(token)] = 0;
token.safeTransfer(to, amount);
}
}

Expand All @@ -167,7 +193,7 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
uint256 chainId,
IERC20 token,
uint256 amount
) external nonReentrant whenNotPaused {
) external nonReentrant whenNotPaused legacySendEnabled {
emit TokenDeposit(to, chainId, token, amount);
token.safeTransferFrom(msg.sender, address(this), amount);
}
Expand All @@ -184,7 +210,7 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
uint256 chainId,
ERC20Burnable token,
uint256 amount
) external nonReentrant whenNotPaused {
) external nonReentrant whenNotPaused legacySendEnabled {
emit TokenRedeem(to, chainId, token, amount);
token.burnFrom(msg.sender, amount);
}
Expand All @@ -207,6 +233,20 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
require(hasRole(NODEGROUP_ROLE, msg.sender), "Caller is not a node group");
require(amount > fee, "Amount must be greater than fee");
require(!kappaMap[kappa], "Kappa is already present");
_withdraw(to, token, amount, fee, kappa);
}

/**
* @dev Common internal logic for withdraw and withdrawAndRemove (once legacy workflows are disabled).
* Note: all security checks are handled outside of this function.
*/
function _withdraw(
address to,
IERC20 token,
uint256 amount,
uint256 fee,
bytes32 kappa
) internal {
kappaMap[kappa] = true;
fees[address(token)] = fees[address(token)].add(fee);
if (address(token) == WETH_ADDRESS && WETH_ADDRESS != address(0)) {
Expand Down Expand Up @@ -239,14 +279,25 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
require(hasRole(NODEGROUP_ROLE, msg.sender), "Caller is not a node group");
require(amount > fee, "Amount must be greater than fee");
require(!kappaMap[kappa], "Kappa is already present");
_mint(to, token, amount, fee, kappa);
}

/**
* @dev Common internal logic for mint and mintAndSwap (once legacy workflows are disabled).
* Note: all security checks are handled outside of this function.
*/
function _mint(
address payable to,
IERC20Mintable token,
uint256 amount,
uint256 fee,
bytes32 kappa
) internal {
kappaMap[kappa] = true;
fees[address(token)] = fees[address(token)].add(fee);
emit TokenMint(to, token, amount.sub(fee), fee, kappa);
token.mint(address(this), amount);
IERC20(token).safeTransfer(to, amount.sub(fee));
if (chainGasAmount != 0 && address(this).balance > chainGasAmount) {
to.call.value(chainGasAmount)("");
}
}

/**
Expand All @@ -269,7 +320,7 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
uint8 tokenIndexTo,
uint256 minDy,
uint256 deadline
) external nonReentrant whenNotPaused {
) external nonReentrant whenNotPaused legacySendEnabled {
emit TokenDepositAndSwap(to, chainId, token, amount, tokenIndexFrom, tokenIndexTo, minDy, deadline);
token.safeTransferFrom(msg.sender, address(this), amount);
}
Expand All @@ -294,7 +345,7 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
uint8 tokenIndexTo,
uint256 minDy,
uint256 deadline
) external nonReentrant whenNotPaused {
) external nonReentrant whenNotPaused legacySendEnabled {
emit TokenRedeemAndSwap(to, chainId, token, amount, tokenIndexFrom, tokenIndexTo, minDy, deadline);
token.burnFrom(msg.sender, amount);
}
Expand All @@ -317,7 +368,7 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
uint8 swapTokenIndex,
uint256 swapMinAmount,
uint256 swapDeadline
) external nonReentrant whenNotPaused {
) external nonReentrant whenNotPaused legacySendEnabled {
emit TokenRedeemAndRemove(to, chainId, token, amount, swapTokenIndex, swapMinAmount, swapDeadline);
token.burnFrom(msg.sender, amount);
}
Expand Down Expand Up @@ -351,12 +402,12 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
require(hasRole(NODEGROUP_ROLE, msg.sender), "Caller is not a node group");
require(amount > fee, "Amount must be greater than fee");
require(!kappaMap[kappa], "Kappa is already present");
// Fallback to regular mint if legacy workflows are disabled.
if (isLegacySendDisabled) {
return _mint(to, token, amount, fee, kappa);
}
kappaMap[kappa] = true;
fees[address(token)] = fees[address(token)].add(fee);
// Transfer gas airdrop
if (chainGasAmount != 0 && address(this).balance > chainGasAmount) {
to.call.value(chainGasAmount)("");
}
// first check to make sure more will be given than min amount required
uint256 expectedOutput = ISwap(pool).calculateSwap(tokenIndexFrom, tokenIndexTo, amount.sub(fee));

Expand Down Expand Up @@ -459,6 +510,10 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
require(hasRole(NODEGROUP_ROLE, msg.sender), "Caller is not a node group");
require(amount > fee, "Amount must be greater than fee");
require(!kappaMap[kappa], "Kappa is already present");
// Fallback to regular withdraw if legacy workflows are disabled.
if (isLegacySendDisabled) {
return _withdraw(to, token, amount, fee, kappa);
}
kappaMap[kappa] = true;
fees[address(token)] = fees[address(token)].add(fee);
// first check to make sure more will be given than min amount required
Expand Down Expand Up @@ -526,7 +581,7 @@ contract SynapseBridge is Initializable, AccessControlUpgradeable, ReentrancyGua
uint256 chainId,
ERC20Burnable token,
uint256 amount
) external nonReentrant whenNotPaused {
) external nonReentrant whenNotPaused legacySendEnabled {
emit TokenRedeemV2(to, chainId, token, amount);
token.burnFrom(msg.sender, amount);
}
Expand Down
16 changes: 16 additions & 0 deletions test/bridge/legacy/PoolMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

// solhint-disable no-empty-blocks, no-unused-vars
contract PoolMock {
/// @notice We include an empty "test" function so that this contract does not appear in the coverage report.
function testPoolMock() external {}

function calculateSwap(
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 amount
) external returns (uint256) {}

function calculateRemoveLiquidityOneToken(uint256 tokenAmount, uint8 tokenIndex) external returns (uint256) {}
}
41 changes: 41 additions & 0 deletions test/bridge/legacy/ReenteringToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import {SynapseERC20} from "../../../contracts/bridge/SynapseERC20.sol";

import {Address} from "@openzeppelin/contracts/utils/Address.sol";

contract ReenteringToken is SynapseERC20 {
address internal target;
bytes internal data;

function setReenteringData(address target_, bytes memory data_) public {
target = target_;
data = data_;
}

function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual override {
super._transfer(sender, recipient, amount);
_reenterTarget();
}

function _mint(address account, uint256 amount) internal virtual override {
super._mint(account, amount);
_reenterTarget();
}

function _reenterTarget() internal {
if (target != address(0)) {
address target_ = target;
bytes memory data_ = data;
delete target;
delete data;
Address.functionCall(target_, data_);
}
}
}
Loading
Loading