Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 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
91d70fc
SYN-94: deploy new implementation (#344)
ChiTimesChi Sep 10, 2026
3c74130
feat: new deploys
ChiTimesChi Sep 10, 2026
7ba447e
fix: foundry config
ChiTimesChi Sep 10, 2026
480f7fa
feat: SYN token deployment and configuration scripts
ChiTimesChi Sep 10, 2026
332dc7a
feat: HyperEVM config and testnet SYN deployments
ChiTimesChi Sep 11, 2026
2af4d09
feat: testnet SynapseAdapter and CREATE2 factory deployments
ChiTimesChi Sep 11, 2026
233b063
feat: HyperEVM mainnet SYN deployment
ChiTimesChi Sep 13, 2026
ee7d485
feat: HyperEVM adapter and multisig deployments
ChiTimesChi Sep 15, 2026
7277489
fix: stop saving deployments after failed Forge runs
ChiTimesChi Sep 15, 2026
82bbd2d
Merge branch 'feat/hyperevm' into feat/syn-bridge-unified
ChiTimesChi Sep 15, 2026
acac535
fix: pin Foundry CI to v1.3.6
ChiTimesChi Sep 15, 2026
cc52618
fix: deployment ABI handling and directory iteration
ChiTimesChi Sep 15, 2026
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ FANTOM_ETHERSCAN_URL=https://api.ftmscan.com/api
FANTOM_ETHERSCAN_KEY=PasteYourOwnKeyPlease
# Harmony
HARMONY_API=https://api.s0.t.hmny.io
# HyperEVM
HYPEREVM_API=https://rpc.hyperliquid.xyz/evm
# Klaytn
KLATYN_API=https://public-node-api.klaytnapi.com/v1/cypress
# Linea
Expand Down Expand Up @@ -119,6 +121,8 @@ BNB_TESTNET_ETHERSCAN_KEY=YourBscScanKey
ETH_SEPOLIA_API=https://rpc.sepolia.org
ETH_SEPOLIA_ETHERSCAN_URL=https://api-sepolia.etherscan.io/api
ETH_SEPOLIA_ETHERSCAN_KEY=YourEtherScanKey
# HyperEVM Testnet
HYPEREVM_TESTNET_API=https://rpc.hyperliquid-testnet.xyz/evm
# Optimism Sepolia
OP_SEPOLIA_API=https://optimism-sepolia.blockpi.network/v1/rpc/public
OP_SEPOLIA_ETHERSCAN_URL=https://api-sepolia-optimistic.etherscan.io/api
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
version: v1.3.6

- name: Run tests
run: ./script/foundry-base.sh
Expand Down Expand Up @@ -100,7 +100,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
version: v1.3.6

- name: Build contracts
run: forge build
Expand Down
94 changes: 76 additions & 18 deletions contracts/bridge/SynapseBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,26 @@ 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");
_;
}

/// @dev We add initializer modifier to constructor to prevent implementation from being initialized
constructor() public initializer {}

receive() external payable {}

Expand All @@ -44,8 +56,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 +144,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 +166,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 +196,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 +213,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 +236,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 +282,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 +323,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 +348,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 +371,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 +405,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 +513,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 +584,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
Loading
Loading