Skip to content

Commit

Permalink
Restructure and update repo (#31)
Browse files Browse the repository at this point in the history
* restructure repo in line with new, expanded purpose

* fix typo

* bump solc version to 8.19 (last version before shanghai)

* move remappings into foundry.toml

* fix foundry.toml syntax mishap

* Update README.md

Co-authored-by: Csongor Kiss <[email protected]>

* Update README.md

Co-authored-by: Csongor Kiss <[email protected]>

* updated README

* fix failing tests

* fix vm.assume rejected too many inputs error in query tests

---------

Co-authored-by: Csongor Kiss <[email protected]>
  • Loading branch information
nonergodic and kcsongor authored Aug 22, 2024
1 parent b9e129e commit e7bdde9
Show file tree
Hide file tree
Showing 33 changed files with 292 additions and 261 deletions.
42 changes: 30 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,55 @@
# Wormhole Solidity SDK

The purpose of this SDK is to provide helpers to take your existing single-chain solidity application cross-chain using Wormhole's automatic relayers
The purpose of this SDK is to make on-chain integrations with Wormhole on EVM compatible chains as smooth as possible by providing all necessary Solidity interfaces along with useful libraries and tools for testing.

### Installation
For off-chain code, please refer to the [TypeScript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts) and in particular the [EVM platform implementation](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/platforms/evm).

This SDK was originally created for integrations with the WormholeRelayer and then expanded to cover all integration.

## Installation

**Foundry and Forge**

```bash
forge install wormhole-foundation/wormhole-solidity-sdk
```

### Example Usage + Introduction to Automatic Relayers
**Solc Version**

Currently the SDK uses solc version 0.8.19 to avoid issues with PUSH0 which was introduced in 0.8.20 but which is not supported on many EVM chains.

## WormholeRelayer

### Introduction

The WormholeRelayer (also sometimes referred to as the automatic or generic relayer) allows integrators to leverage external parties known as delivery providers, to relay messages emitted on a given source chain to the intended target chain.

This frees integrators, who are building a cross-chain app, from the cumbersome and painful task of having to run relaying infrastructure themselves (and thus e.g. dealing with the headache of having to acquire gas tokens for the target chain).

Messages include, but aren't limited to: Wormhole attestations (VAAs), Circle attestations (CCTP)

Delivery providers provide a quote for the cost of a delivery on the source chain and also take payment there. This means the process is not fully trustless (delivery providers can take payment and then fail to perform the delivery), however the state of the respective chains always makes it clear whether a delivery provider has done their duty for a given delivery and delivery providers can't maliciously manipulate the content of a delivery.

### Example Usage

[HelloWormhole - Simple cross-chain message sending application](https://github.com/wormhole-foundation/hello-wormhole)

[HelloToken - Simple cross-chain token sending application](https://github.com/wormhole-foundation/hello-tokens)
[HelloToken - Simple cross-chain token sending application](https://github.com/wormhole-foundation/hello-token)

[HelloUSDC - Simple cross-chain USDC sending application using CCTP](https://github.com/wormhole-foundation/hello-usdc)

### SDK Summary

- Includes interfaces to interact with contracts in the Wormhole ecosystem ([src/interfaces](https://github.com/wormhole-foundation/wormhole-solidity-sdk/tree/main/src/interfaces))
- Includes the base class ‘Base’ with helpers for common actions that will typically need to be done within ‘receiveWormholeMessages’:
- [`onlyWormholeRelayer()`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/Base.sol#L24): Checking that msg.sender is the wormhole relayer contract
Sometimes, Cross-chain applications may be set up such that there is one ‘spoke’ contract on every chain, which sends messages to the ‘hub’ contract. If so, we’d ideally only want to allow messages to be sent from these spoke contracts. Included are helpers for this:

- [`setRegisteredSender(uint16 sourceChain, bytes32 sourceAddress)`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/Base.sol#L47): Setting the specified sender for ‘sourceChain’ to be ‘sourceAddress’
- [`isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress)`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/Base.sol#L35): Checking that the sender who requested the delivery is the registered address for that chain
- [`onlyWormholeRelayer()`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/Base.sol#L24): Checking that msg.sender is the wormhole relayer contract
Sometimes, cross-chain applications may be set up such that there is one ‘spoke’ contract on every chain, which sends messages to the ‘hub’ contract. If so, we’d ideally only want to allow messages to be sent from these spoke contracts. Included are helpers for this:

Look at test/Counter.t.sol for an example usage of Base
- [`setRegisteredSender(uint16 sourceChain, bytes32 sourceAddress)`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/Base.sol#L45): Setting the specified sender for ‘sourceChain’ to be ‘sourceAddress’
- [`isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress)`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/Base.sol#L30): Checking that the sender who requested the delivery is the registered address for that chain

- Included are also the ‘[TokenSender](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/TokenBase#L36)’ and ‘[TokenReceiver](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/TokenBase.sol#L126)’ base classes with helpers for smart contracts that wish to send and receive tokens using Wormhole’s TokenBridge. See ‘[HelloToken](https://github.com/wormhole-foundation/hello-token)’ for example usage.
- Included are also the ‘[CCTPSender](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/CCTPBase#L70)’ and ‘[CCTPReceiver](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/CCTPBase.sol#L134)’ base classes with helpers for smart contracts that wish to send and receive both tokens using Wormhole’s TokenBridge as well as USDC using CCTP. See ‘[HelloUSDC](https://github.com/wormhole-foundation/hello-usdc)’ for example usage.
- Included are also the ‘[TokenSender](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/TokenBase#L36)’ and ‘[TokenReceiver](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/TokenBase.sol#L126)’ base classes with helpers for smart contracts that wish to send and receive tokens using Wormhole’s TokenBridge. See ‘[HelloToken](https://github.com/wormhole-foundation/hello-token)’ for example usage.
- Included are also the ‘[CCTPSender](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/CCTPBase#L59)’ and ‘[CCTPReceiver](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/CCTPBase.sol#L177)’ base classes with helpers for smart contracts that wish to send and receive both tokens using Wormhole’s TokenBridge as well as USDC using CCTP. See ‘[HelloUSDC](https://github.com/wormhole-foundation/hello-usdc)’ for example usage.
- Included are helpers that help set up a local forge testing environment. See ‘[HelloWormhole](https://github.com/wormhole-foundation/hello-wormhole)’ for example usage.

**Note: This code is meant to be used as starter / reference code. Feel free to modify for use in your contracts, and also make sure to audit any code used from here as part of your contracts before deploying to mainnet.**
10 changes: 8 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
[profile.default]
solc_version = "0.8.13"
solc_version = "0.8.19"
src = "src"
out = "out"
libs = ["lib"]
via_ir = true

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
remappings = [
"ds-test/=lib/forge-std/lib/ds-test/src/",
"forge-std/=lib/forge-std/src/",
"wormhole-sdk/=src/",
]

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
2 changes: 0 additions & 2 deletions remappings.txt

This file was deleted.

2 changes: 1 addition & 1 deletion src/Chains.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;
pragma solidity ^0.8.19;

// In the wormhole wire format, 0 indicates that a message is for any destination chain
uint16 constant CHAIN_ID_UNSET = 0;
Expand Down
18 changes: 10 additions & 8 deletions src/Utils.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;
pragma solidity ^0.8.19;

import "./interfaces/IWormholeRelayer.sol";
error NotAnEvmAddress(bytes32);

function toWormholeFormat(address addr) pure returns (bytes32) {
return bytes32(uint256(uint160(addr)));
function toUniversalAddress(address addr) pure returns (bytes32 universalAddr) {
universalAddr = bytes32(uint256(uint160(addr)));
}

function fromWormholeFormat(bytes32 whFormatAddress) pure returns (address) {
if (uint256(whFormatAddress) >> 160 != 0) {
revert NotAnEvmAddress(whFormatAddress);
function fromUniversalAddress(bytes32 universalAddr) pure returns (address addr) {
if (bytes12(universalAddr) != 0)
revert NotAnEvmAddress(universalAddr);

assembly ("memory-safe") {
addr := universalAddr
}
return address(uint160(uint256(whFormatAddress)));
}
10 changes: 5 additions & 5 deletions src/Base.sol → src/WormholeRelayer/Base.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;
pragma solidity ^0.8.19;

import "./interfaces/IWormholeReceiver.sol";
import "./interfaces/IWormholeRelayer.sol";
import "./interfaces/IWormhole.sol";
import "./Utils.sol";
import "wormhole-sdk/interfaces/IWormholeReceiver.sol";
import "wormhole-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-sdk/interfaces/IWormhole.sol";
import "wormhole-sdk/Utils.sol";

abstract contract Base {
IWormholeRelayer public immutable wormholeRelayer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;
pragma solidity ^0.8.19;

import "./interfaces/IWormholeReceiver.sol";
import "./interfaces/IWormholeRelayer.sol";
import "./interfaces/ITokenBridge.sol";
import {IERC20} from "./interfaces/IERC20.sol";
import "./interfaces/CCTPInterfaces/ITokenMessenger.sol";
import "./interfaces/CCTPInterfaces/IMessageTransmitter.sol";
import "wormhole-sdk/interfaces/IWormholeReceiver.sol";
import "wormhole-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-sdk/interfaces/ITokenBridge.sol";
import "wormhole-sdk/interfaces/token/IERC20.sol";
import "wormhole-sdk/interfaces/cctp/ITokenMessenger.sol";
import "wormhole-sdk/interfaces/cctp/IMessageTransmitter.sol";
import "wormhole-sdk/Utils.sol";

import "./Utils.sol";
import "./TokenBase.sol";
import "./CCTPBase.sol";

Expand Down Expand Up @@ -216,13 +216,13 @@ abstract contract CCTPAndTokenSender is CCTPAndTokenBase {
token,
amount,
targetChain,
toWormholeFormat(targetAddress),
toUniversalAddress(targetAddress),
0,
payload
);
return
VaaKey({
emitterAddress: toWormholeFormat(address(tokenBridge)),
emitterAddress: toUniversalAddress(address(tokenBridge)),
chainId: wormhole.chainId(),
sequence: sequence
});
Expand Down Expand Up @@ -333,7 +333,7 @@ abstract contract CCTPAndTokenReceiver is CCTPAndTokenBase {
) internal view returns (address tokenAddressOnThisChain) {
return
tokenHomeChain == wormhole.chainId()
? fromWormholeFormat(tokenHomeAddress)
? fromUniversalAddress(tokenHomeAddress)
: tokenBridge.wrappedAsset(tokenHomeChain, tokenHomeAddress);
}

Expand Down Expand Up @@ -362,7 +362,7 @@ abstract contract CCTPAndTokenReceiver is CCTPAndTokenBase {
ITokenBridge.TransferWithPayload memory transfer = tokenBridge
.parseTransferWithPayload(parsed.payload);
require(
transfer.to == toWormholeFormat(address(this)) &&
transfer.to == toUniversalAddress(address(this)) &&
transfer.toChain == wormhole.chainId(),
"Token was not sent to this address"
);
Expand Down
14 changes: 7 additions & 7 deletions src/CCTPBase.sol → src/WormholeRelayer/CCTPBase.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;
pragma solidity ^0.8.19;

import "./interfaces/IWormholeReceiver.sol";
import "./interfaces/IWormholeRelayer.sol";
import {IERC20} from "./interfaces/IERC20.sol";
import "./interfaces/CCTPInterfaces/ITokenMessenger.sol";
import "./interfaces/CCTPInterfaces/IMessageTransmitter.sol";
import "wormhole-sdk/interfaces/IWormholeReceiver.sol";
import "wormhole-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-sdk/interfaces/token/IERC20.sol";
import "wormhole-sdk/interfaces/cctp/ITokenMessenger.sol";
import "wormhole-sdk/interfaces/cctp/IMessageTransmitter.sol";
import "wormhole-sdk/Utils.sol";

import "./Utils.sol";
import "./Base.sol";

library CCTPMessageLib {
Expand Down
22 changes: 11 additions & 11 deletions src/TokenBase.sol → src/WormholeRelayer/TokenBase.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;
pragma solidity ^0.8.19;

import "./interfaces/IWormholeReceiver.sol";
import "./interfaces/IWormholeRelayer.sol";
import "./interfaces/ITokenBridge.sol";
import {IERC20} from "./interfaces/IERC20.sol";
import {Base} from "./WormholeRelayerSDK.sol";
import "wormhole-sdk/interfaces/IWormholeReceiver.sol";
import "wormhole-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-sdk/interfaces/ITokenBridge.sol";
import "wormhole-sdk/interfaces/token/IERC20.sol";
import "wormhole-sdk/Utils.sol";

import "./Utils.sol";
import {Base} from "./Base.sol";

abstract contract TokenBase is Base {
ITokenBridge public immutable tokenBridge;
Expand Down Expand Up @@ -76,13 +76,13 @@ abstract contract TokenSender is TokenBase {
token,
amount,
targetChain,
toWormholeFormat(targetAddress),
toUniversalAddress(targetAddress),
0,
payload
);
return
VaaKey({
emitterAddress: toWormholeFormat(address(tokenBridge)),
emitterAddress: toUniversalAddress(address(tokenBridge)),
chainId: wormhole.chainId(),
sequence: sequence
});
Expand Down Expand Up @@ -180,7 +180,7 @@ abstract contract TokenReceiver is TokenBase {
) internal view returns (address tokenAddressOnThisChain) {
return
tokenHomeChain == wormhole.chainId()
? fromWormholeFormat(tokenHomeAddress)
? fromUniversalAddress(tokenHomeAddress)
: tokenBridge.wrappedAsset(tokenHomeChain, tokenHomeAddress);
}

Expand All @@ -205,7 +205,7 @@ abstract contract TokenReceiver is TokenBase {
ITokenBridge.TransferWithPayload memory transfer = tokenBridge
.parseTransferWithPayload(parsed.payload);
require(
transfer.to == toWormholeFormat(address(this)) &&
transfer.to == toUniversalAddress(address(this)) &&
transfer.toChain == wormhole.chainId(),
"Token was not sent to this address"
);
Expand Down
31 changes: 22 additions & 9 deletions src/WormholeRelayerSDK.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;
pragma solidity ^0.8.19;

import "./interfaces/IWormholeReceiver.sol";
import "./interfaces/IWormholeRelayer.sol";
import "./Chains.sol";
import "./Utils.sol";
import {Base} from "./Base.sol";
import {TokenBase, TokenReceiver, TokenSender} from "./TokenBase.sol";
import {CCTPBase, CCTPReceiver, CCTPSender} from "./CCTPBase.sol";
import {CCTPAndTokenBase, CCTPAndTokenReceiver, CCTPAndTokenSender} from "./CCTPAndTokenBase.sol";
import "wormhole-sdk/interfaces/IWormholeReceiver.sol";
import "wormhole-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-sdk/Chains.sol";
import "wormhole-sdk/Utils.sol";

import {Base} from "wormhole-sdk/WormholeRelayer/Base.sol";
import {
TokenBase,
TokenReceiver,
TokenSender
} from "wormhole-sdk/WormholeRelayer/TokenBase.sol";
import {
CCTPBase,
CCTPReceiver,
CCTPSender
} from "wormhole-sdk/WormholeRelayer/CCTPBase.sol";
import {
CCTPAndTokenBase,
CCTPAndTokenReceiver,
CCTPAndTokenSender
} from "wormhole-sdk/WormholeRelayer/CCTPAndTokenBase.sol";
2 changes: 1 addition & 1 deletion src/interfaces/ITokenBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

pragma solidity ^0.8.0;

import "./IWETH.sol";
import "./token/IWETH.sol";
import "./IWormhole.sol";

interface ITokenBridge {
Expand Down
6 changes: 4 additions & 2 deletions src/interfaces/IWormholeRelayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

pragma solidity ^0.8.0;

import {NotAnEvmAddress} from "wormhole-sdk/Utils.sol";

/**
* @title WormholeRelayer
* @author
Expand Down Expand Up @@ -668,5 +670,5 @@ error InvalidOverrideRefundPerGasUnused();
error InsufficientRelayerFunds(uint256 msgValue, uint256 minimum);

//When a bytes32 field can't be converted into a 20 byte EVM address, because the 12 padding bytes
// are non-zero (duplicated from Utils.sol)
error NotAnEvmAddress(bytes32);
// are non-zero (see Utils.sol)
//error NotAnEvmAddress(bytes32);
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@

/*
* Copyright (c) 2022, Circle Internet Financial Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity ^0.8.0;

/**
* @title TokenMessenger
* @notice Sends messages and receives messages to/from MessageTransmitters
* and to/from TokenMinters
*/
interface ITokenMessenger {
/**
* @notice Deposits and burns tokens from sender to be minted on destination domain. The mint
Expand Down
File renamed without changes.
17 changes: 17 additions & 0 deletions src/interfaces/token/IUSDC.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

import "./IERC20.sol";

interface IUSDC is IERC20 {
function mint(address to, uint256 amount) external;

function configureMinter(address minter, uint256 minterAllowedAmount) external;

function masterMinter() external view returns (address);

function owner() external view returns (address);

function blacklister() external view returns (address);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// contracts/Bridge.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/BytesParsing.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.13;
pragma solidity ^0.8.19;

library BytesParsing {
uint256 private constant freeMemoryPtr = 0x40;
Expand Down
4 changes: 2 additions & 2 deletions src/testing/ERC20Mock.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

pragma solidity ^0.8.13;
pragma solidity ^0.8.19;

import "../interfaces/IERC20.sol";
import "wormhole-sdk/interfaces/token/IERC20.sol";

/*
* ERC20 impl from solmate
Expand Down
Loading

0 comments on commit e7bdde9

Please sign in to comment.