Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
178 changes: 178 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Across Protocol Smart Contracts

This repository contains production smart contracts for the Across Protocol cross-chain bridge.

## Development Frameworks

- **Foundry** (primary) - Used for new tests and deployment scripts
- **Hardhat** (legacy) - Some tests still use Hardhat; we're migrating to Foundry

## Project Structure

```
contracts/ # Smart contract source files
chain-adapters/ # L1 chain adapters
interfaces/ # Interface definitions
libraries/ # Shared libraries
test/evm/
foundry/ # Foundry tests (.t.sol)
local/ # Local unit tests
fork/ # Fork tests
hardhat/ # Legacy Hardhat tests (.ts)
script/ # Foundry deployment scripts (.s.sol)
utils/ # Script utilities (Constants.sol, DeploymentUtils.sol)
lib/ # External dependencies (git submodules)
```

## Build & Test Commands

```bash
# Build contracts
forge build # Foundry
yarn build-evm # Hardhat

# Run tests
yarn test-evm-foundry # Foundry local tests (recommended)
FOUNDRY_PROFILE=local forge test # Same as above
yarn test-evm-hardhat # Hardhat tests (legacy)

# Run specific Foundry tests
forge test --match-test testDeposit
forge test --match-contract Router_Adapter
forge test -vvv # Verbose output
```

## Naming Conventions

### Contract Files

- PascalCase with underscores for chain-specific: `Arbitrum_SpokePool.sol`, `OP_Adapter.sol`
- Interfaces: `I` prefix: `ISpokePool.sol`, `IArbitrumBridge.sol`
- Libraries: `<Name>Lib.sol`

### Test Files

- Foundry: `.t.sol` suffix: `Router_Adapter.t.sol`, `Arbitrum_Adapter.t.sol`
- Test contracts: `contract <Name>Test is Test { ... }`
- Test functions: `function test<Description>() public`

### Deployment Scripts

- Numbered with `.s.sol` suffix: `001DeployHubPool.s.sol`, `004DeployArbitrumAdapter.s.sol`
- Script contracts: `contract Deploy<ContractName> is Script, Test, Constants`

## Writing Tests

```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { Test } from "forge-std/Test.sol";
import { MyContract } from "../contracts/MyContract.sol";

contract MyContractTest is Test {
MyContract public myContract;

function setUp() public {
myContract = new MyContract();
}

function testBasicFunctionality() public {
// Test implementation
assertEq(myContract.value(), expected);
}

function testRevertOnInvalidInput() public {
vm.expectRevert();
myContract.doSomething(invalidInput);
}
}
```

### Test Gotchas

- **Mocks**: Check `contracts/test/` for existing mocks before creating new ones (MockCCTP.sol, ArbitrumMocks.sol, etc.)
- **MockSpokePool**: Requires UUPS proxy deployment: `new ERC1967Proxy(address(new MockSpokePool(weth)), abi.encodeCall(MockSpokePool.initialize, (...)))`
- **vm.mockCall pattern** (prefer over custom mocks for simple return values):
```solidity
vm.etch(fakeAddr, hex"00"); // Bypass extcodesize check
vm.mockCall(fakeAddr, abi.encodeWithSelector(SELECTOR), abi.encode(returnVal));
vm.expectCall(fakeAddr, msgValue, abi.encodeWithSelector(SELECTOR, arg1));
```
- **Delegatecall context**: Adapter tests via HubPool emit events from HubPool's address; `vm.expectRevert()` may lose error data

## Deployment Scripts

Scripts follow a numbered pattern and use shared utilities from `script/utils/`.

```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { Script } from "forge-std/Script.sol";
import { Test } from "forge-std/Test.sol";
import { console } from "forge-std/console.sol";
import { Constants } from "./utils/Constants.sol";
import { MyContract } from "../contracts/MyContract.sol";

// How to run:
// 1. `source .env` where `.env` has MNEMONIC="x x x ... x" and ETHERSCAN_API_KEY="x"
// 2. forge script script/00XDeployMyContract.s.sol:DeployMyContract --rpc-url $NODE_URL_1 -vvvv
// 3. Verify simulation works
// 4. Deploy: forge script script/00XDeployMyContract.s.sol:DeployMyContract --rpc-url $NODE_URL_1 --broadcast --verify -vvvv

contract DeployMyContract is Script, Test, Constants {
function run() external {
string memory deployerMnemonic = vm.envString("MNEMONIC");
uint256 deployerPrivateKey = vm.deriveKey(deployerMnemonic, 0);

uint256 chainId = block.chainid;
// Validate chain if needed
require(chainId == getChainId("MAINNET"), "Deploy on mainnet only");

vm.startBroadcast(deployerPrivateKey);

MyContract myContract = new MyContract /* constructor args */();

console.log("Chain ID:", chainId);
console.log("MyContract deployed to:", address(myContract));

vm.stopBroadcast();
}
}
```

For upgradeable contracts, use `DeploymentUtils` which provides `deployNewProxy()`.

## Configuration

See `foundry.toml` for Foundry configuration. Key settings:

- Source: `contracts/`
- Tests: `test/evm/foundry/`
- Solidity: 0.8.30
- EVM: Prague
- Optimizer: 800 runs with via-ir

**Do not modify `foundry.toml` without asking** - explain what you want to change and why.

## Security Practices

- Follow CEI (Checks-Effects-Interactions) pattern
- Use OpenZeppelin for access control and upgrades
- Validate all inputs at system boundaries
- Use `_requireAdminSender()` for admin-only functions
- UUPS proxy pattern for upgradeable contracts
- Cross-chain ownership: HubPool owns all SpokePool contracts

## Linting

```bash
yarn lint-solidity # Solhint for Solidity
yarn lint-js # Prettier for JS/TS
yarn lint-fix # Auto-fix all
```

## License

BUSL-1.1 (see LICENSE file for exceptions)
34 changes: 17 additions & 17 deletions broadcast/deployed-addresses.md
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,14 @@ This file contains the latest deployed smart contract addresses from the broadca

## Monad (143)

| Contract Name | Address |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Helios | [0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64](https://monadscan.com//address/0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64) |
| MulticallHandler | [0xeC41F75c686e376Ab2a4F18bde263ab5822c4511](https://monadscan.com//address/0xeC41F75c686e376Ab2a4F18bde263ab5822c4511) |
| SpokePool | [0xd2ecb3afe598b746F8123CaE365a598DA831A449](https://monadscan.com//address/0xd2ecb3afe598b746F8123CaE365a598DA831A449) |
| SpokePoolPeriphery | [0xe9b0666DFfC176Df6686726CB9aaC78fD83D20d7](https://monadscan.com//address/0xe9b0666DFfC176Df6686726CB9aaC78fD83D20d7) |
| SponsoredCCTPSrcPeriphery | [0xCbf361EE59Cc74b9d6e7Af947fe4136828faf2C5](https://monadscan.com//address/0xCbf361EE59Cc74b9d6e7Af947fe4136828faf2C5) |
| SponsoredOFTSrcPeriphery | [0xa3dE5F042EFD4C732498883100A2d319BbB3c1A1](https://monadscan.com//address/0xa3dE5F042EFD4C732498883100A2d319BbB3c1A1) |
| Contract Name | Address |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Helios | [0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64](https://monadvision.com/address/0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64) |
| MulticallHandler | [0xeC41F75c686e376Ab2a4F18bde263ab5822c4511](https://monadvision.com/address/0xeC41F75c686e376Ab2a4F18bde263ab5822c4511) |
| SpokePool | [0xd2ecb3afe598b746F8123CaE365a598DA831A449](https://monadvision.com/address/0xd2ecb3afe598b746F8123CaE365a598DA831A449) |
| SpokePoolPeriphery | [0xe9b0666DFfC176Df6686726CB9aaC78fD83D20d7](https://monadvision.com/address/0xe9b0666DFfC176Df6686726CB9aaC78fD83D20d7) |
| SponsoredCCTPSrcPeriphery | [0xCbf361EE59Cc74b9d6e7Af947fe4136828faf2C5](https://monadvision.com/address/0xCbf361EE59Cc74b9d6e7Af947fe4136828faf2C5) |
| SponsoredOFTSrcPeriphery | [0xa3dE5F042EFD4C732498883100A2d319BbB3c1A1](https://monadvision.com/address/0xa3dE5F042EFD4C732498883100A2d319BbB3c1A1) |

## Lens (232)

Expand All @@ -143,10 +143,10 @@ This file contains the latest deployed smart contract addresses from the broadca

| Contract Name | Address |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| MulticallHandler | [0x68d3806E57148D6c6793C78EbDDbc272fE605dbf](https://era.zksync.network/address/0x68d3806E57148D6c6793C78EbDDbc272fE605dbf) |
| SpokePool | [0xE0B015E54d54fc84a6cB9B666099c46adE9335FF](https://era.zksync.network/address/0xE0B015E54d54fc84a6cB9B666099c46adE9335FF) |
| SpokePoolPeriphery | [0x672b9ba0CE73b69b5F940362F0ee36AAA3F02986](https://era.zksync.network/address/0x672b9ba0CE73b69b5F940362F0ee36AAA3F02986) |
| SpokePoolVerifier | [0x3Fb9cED51E968594C87963a371Ed90c39519f65A](https://era.zksync.network/address/0x3Fb9cED51E968594C87963a371Ed90c39519f65A) |
| MulticallHandler | [0x68d3806E57148D6c6793C78EbDDbc272fE605dbf](https://explorer.zksync.io/address/0x68d3806E57148D6c6793C78EbDDbc272fE605dbf) |
| SpokePool | [0xE0B015E54d54fc84a6cB9B666099c46adE9335FF](https://explorer.zksync.io/address/0xE0B015E54d54fc84a6cB9B666099c46adE9335FF) |
| SpokePoolPeriphery | [0x672b9ba0CE73b69b5F940362F0ee36AAA3F02986](https://explorer.zksync.io/address/0x672b9ba0CE73b69b5F940362F0ee36AAA3F02986) |
| SpokePoolVerifier | [0x3Fb9cED51E968594C87963a371Ed90c39519f65A](https://explorer.zksync.io/address/0x3Fb9cED51E968594C87963a371Ed90c39519f65A) |

## World Chain (480)

Expand Down Expand Up @@ -357,11 +357,11 @@ This file contains the latest deployed smart contract addresses from the broadca

## Tatara (129399)

| Contract Name | Address |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| MulticallHandler | [0xAC537C12fE8f544D712d71ED4376a502EEa944d7](https://explorer.tatara.katana.network/address/0xAC537C12fE8f544D712d71ED4376a502EEa944d7) |
| SpokePool | [0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64](https://explorer.tatara.katana.network/address/0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64) |
| SpokePoolVerifier | [0x630b76C7cA96164a5aCbC1105f8BA8B739C82570](https://explorer.tatara.katana.network/address/0x630b76C7cA96164a5aCbC1105f8BA8B739C82570) |
| Contract Name | Address |
| ----------------- | ------------------------------------------ |
| MulticallHandler | 0xAC537C12fE8f544D712d71ED4376a502EEa944d7 |
| SpokePool | 0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64 |
| SpokePoolVerifier | 0x630b76C7cA96164a5aCbC1105f8BA8B739C82570 |

## Arbitrum Sepolia (421614)

Expand Down
141 changes: 120 additions & 21 deletions contracts/test/ArbitrumMocks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,144 @@
pragma solidity ^0.8.0;

contract ArbitrumMockErc20GatewayRouter {
address public gateway;

event OutboundTransferCalled(
address l1Token,
address to,
uint256 amount,
uint256 maxGas,
uint256 gasPriceBid,
bytes data
);

event OutboundTransferCustomRefundCalled(
address l1Token,
address refundTo,
address to,
uint256 amount,
uint256 maxGas,
uint256 gasPriceBid,
bytes data
);

// Call tracking for test assertions (smock-like behavior)
uint256 public outboundTransferCustomRefundCallCount;

struct OutboundTransferCustomRefundCall {
address l1Token;
address refundTo;
address to;
uint256 amount;
uint256 maxGas;
uint256 gasPriceBid;
bytes data;
}
OutboundTransferCustomRefundCall public lastOutboundTransferCustomRefundCall;

function setGateway(address _gateway) external {
gateway = _gateway;
}

function outboundTransferCustomRefund(
address,
address,
address,
uint256,
uint256,
uint256,
address _l1Token,
address _refundTo,
address _to,
uint256 _amount,
uint256 _maxGas,
uint256 _gasPriceBid,
bytes calldata _data
) external payable returns (bytes memory) {
outboundTransferCustomRefundCallCount++;
lastOutboundTransferCustomRefundCall = OutboundTransferCustomRefundCall(
_l1Token,
_refundTo,
_to,
_amount,
_maxGas,
_gasPriceBid,
_data
);
emit OutboundTransferCustomRefundCalled(_l1Token, _refundTo, _to, _amount, _maxGas, _gasPriceBid, _data);
return _data;
}

function outboundTransfer(
address,
address,
uint256,
uint256,
uint256,
address _l1Token,
address _to,
uint256 _amount,
uint256 _maxGas,
uint256 _gasPriceBid,
bytes calldata _data
) external payable returns (bytes memory) {
emit OutboundTransferCalled(_l1Token, _to, _amount, _maxGas, _gasPriceBid, _data);
return _data;
}

function getGateway(address) external view returns (address) {
return address(this);
// Return custom gateway if set, otherwise return self (original behavior)
return gateway != address(0) ? gateway : address(this);
}
}

contract Inbox {
event RetryableTicketCreated(
address destAddr,
uint256 l2CallValue,
uint256 maxSubmissionCost,
address excessFeeRefundAddress,
address callValueRefundAddress,
uint256 maxGas,
uint256 gasPriceBid,
bytes data
);

// Call tracking for test assertions (smock-like behavior)
uint256 public createRetryableTicketCallCount;

struct CreateRetryableTicketCall {
address destAddr;
uint256 l2CallValue;
uint256 maxSubmissionCost;
address excessFeeRefundAddress;
address callValueRefundAddress;
uint256 maxGas;
uint256 gasPriceBid;
bytes data;
}
CreateRetryableTicketCall public lastCreateRetryableTicketCall;

function createRetryableTicket(
address,
uint256,
uint256,
address,
address,
uint256,
uint256,
bytes memory
) external pure returns (uint256) {
address _destAddr,
uint256 _l2CallValue,
uint256 _maxSubmissionCost,
address _excessFeeRefundAddress,
address _callValueRefundAddress,
uint256 _maxGas,
uint256 _gasPriceBid,
bytes memory _data
) external payable returns (uint256) {
createRetryableTicketCallCount++;
lastCreateRetryableTicketCall = CreateRetryableTicketCall(
_destAddr,
_l2CallValue,
_maxSubmissionCost,
_excessFeeRefundAddress,
_callValueRefundAddress,
_maxGas,
_gasPriceBid,
_data
);
emit RetryableTicketCreated(
_destAddr,
_l2CallValue,
_maxSubmissionCost,
_excessFeeRefundAddress,
_callValueRefundAddress,
_maxGas,
_gasPriceBid,
_data
);
return 0;
}
}
Expand Down
Loading
Loading