Set up proxy contract and forge
This commit is contained in:
parent
30453c7953
commit
cf2b4884cd
|
@ -3,3 +3,7 @@
|
||||||
/lib/
|
/lib/
|
||||||
/node_modules/
|
/node_modules/
|
||||||
addresses.json
|
addresses.json
|
||||||
|
anvil.log
|
||||||
|
deploy.out
|
||||||
|
cache
|
||||||
|
broadcast
|
||||||
|
|
|
@ -6,70 +6,34 @@ pragma solidity >=0.8.0 <0.9.0;
|
||||||
import "./libraries/external/BytesLib.sol";
|
import "./libraries/external/BytesLib.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
|
||||||
interface IWormhole {
|
import "./interfaces/ITokenBridge.sol";
|
||||||
function publishMessage(
|
|
||||||
uint32 nonce,
|
|
||||||
bytes memory payload,
|
|
||||||
uint8 consistencyLevel
|
|
||||||
) external payable returns (uint64 sequence);
|
|
||||||
function messageFee() external view returns (uint256);
|
|
||||||
}
|
|
||||||
interface ITokenBridge {
|
|
||||||
function wrapAndTransferETH(
|
|
||||||
uint16 recipientChain,
|
|
||||||
bytes32 recipient,
|
|
||||||
uint256 arbiterFee,
|
|
||||||
uint32 nonce
|
|
||||||
) external payable returns (uint64 sequence);
|
|
||||||
function chainId() external view returns (uint16);
|
|
||||||
function WETH() external view returns (IWETH);
|
|
||||||
}
|
|
||||||
interface IWETH is IERC20 {
|
|
||||||
function deposit() external payable;
|
|
||||||
function withdraw(uint amount) external;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reuse Portal Transfer struct for convenience
|
import "./FastTransferMessages.sol";
|
||||||
struct Transfer {
|
import "./FastTransferSetters.sol";
|
||||||
// PayloadID uint8 = 1
|
|
||||||
uint8 payloadID;
|
|
||||||
// Amount being transferred (big-endian uint256)
|
|
||||||
uint256 amount;
|
|
||||||
// Address of the token. Left-zero-padded if shorter than 32 bytes
|
|
||||||
bytes32 tokenAddress;
|
|
||||||
// Chain ID of the token
|
|
||||||
uint16 tokenChain;
|
|
||||||
// Address of the recipient. Left-zero-padded if shorter than 32 bytes
|
|
||||||
bytes32 to;
|
|
||||||
// Chain ID of the recipient
|
|
||||||
uint16 toChain;
|
|
||||||
// Amount of tokens (big-endian uint256) that the user is willing to pay as relayer fee. Must be <= Amount.
|
|
||||||
uint256 fee;
|
|
||||||
}
|
|
||||||
|
|
||||||
contract FastTransfer {
|
|
||||||
|
|
||||||
IWormhole wormhole;
|
|
||||||
ITokenBridge portal;
|
|
||||||
|
|
||||||
constructor(address wormholeAddress, address portalAddress) {
|
|
||||||
wormhole = IWormhole(wormholeAddress);
|
|
||||||
portal = ITokenBridge(portalAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
contract FastTransfer is FastTransferMessages, FastTransferSetters {
|
||||||
function wrapAndTransferETH(
|
function wrapAndTransferETH(
|
||||||
uint16 recipientChain,
|
uint16 recipientChain,
|
||||||
bytes32 recipient,
|
bytes32 recipient,
|
||||||
uint256 arbiterFee,
|
uint256 arbiterFee,
|
||||||
uint32 nonce
|
uint32 nonce
|
||||||
) public payable returns (uint64 fastSequence, uint64 portalSequence) {
|
) public payable returns (uint64 fastSequence, uint64 portalSequence) {
|
||||||
|
// cache Wormhole and Portal instance
|
||||||
|
IWormhole wormhole = wormhole();
|
||||||
|
ITokenBridge portal = portal();
|
||||||
|
|
||||||
// Portal accounts for 1 fee, but we must account for 2
|
// Portal accounts for 1 fee, but we must account for 2
|
||||||
uint wormholeFee = wormhole.messageFee();
|
uint wormholeFee = wormhole.messageFee();
|
||||||
require(wormholeFee * 2 < msg.value, "value is smaller than wormhole fees");
|
require(wormholeFee * 2 < msg.value, "value is smaller than wormhole fees");
|
||||||
|
|
||||||
|
// compute amound less fees
|
||||||
uint amount = msg.value - wormholeFee * 2;
|
uint amount = msg.value - wormholeFee * 2;
|
||||||
// Portal will normalize the amount to 8 decimals, so we should do the same
|
|
||||||
|
// normalize amount the same way that Portal does
|
||||||
uint normalizedAmount = normalizeAmount(amount, 18);
|
uint normalizedAmount = normalizeAmount(amount, 18);
|
||||||
Transfer memory fastTransfer = Transfer({
|
|
||||||
|
// create fast transfer message and publish it
|
||||||
|
ITokenBridge.Transfer memory fastTransfer = ITokenBridge.Transfer({
|
||||||
payloadID: 1,
|
payloadID: 1,
|
||||||
amount: normalizedAmount,
|
amount: normalizedAmount,
|
||||||
tokenAddress: bytes32(uint256(uint160(address(portal.WETH())))),
|
tokenAddress: bytes32(uint256(uint160(address(portal.WETH())))),
|
||||||
|
@ -78,21 +42,15 @@ contract FastTransfer {
|
||||||
toChain: recipientChain,
|
toChain: recipientChain,
|
||||||
fee: 0
|
fee: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
fastSequence = wormhole.publishMessage{
|
fastSequence = wormhole.publishMessage{
|
||||||
value : wormholeFee
|
value : wormholeFee
|
||||||
}(
|
}(
|
||||||
0,
|
nonce,
|
||||||
abi.encodePacked(
|
encodeFastTransfer(fastTransfer),
|
||||||
fastTransfer.payloadID,
|
finality(true)
|
||||||
fastTransfer.amount,
|
|
||||||
fastTransfer.tokenAddress,
|
|
||||||
fastTransfer.tokenChain,
|
|
||||||
fastTransfer.to,
|
|
||||||
fastTransfer.toChain,
|
|
||||||
fastTransfer.fee
|
|
||||||
),
|
|
||||||
200
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Forward the remaining value sans the first fee
|
// Forward the remaining value sans the first fee
|
||||||
portalSequence = portal.wrapAndTransferETH{
|
portalSequence = portal.wrapAndTransferETH{
|
||||||
value: msg.value - wormholeFee
|
value: msg.value - wormholeFee
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity >=0.8.0 <0.9.0;
|
||||||
|
|
||||||
|
import "./libraries/external/BytesLib.sol";
|
||||||
|
|
||||||
|
import "./interfaces/ITokenBridge.sol";
|
||||||
|
|
||||||
|
import "./FastTransferState.sol";
|
||||||
|
|
||||||
|
contract FastTransferGetters is FastTransferState {
|
||||||
|
function owner() public view returns (address) {
|
||||||
|
return _state.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInitialized(address impl) public view returns (bool) {
|
||||||
|
return _state.initializedImplementations[impl];
|
||||||
|
}
|
||||||
|
|
||||||
|
function chainId() public view returns (uint16) {
|
||||||
|
return _state.chainId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wormhole() internal view returns (IWormhole) {
|
||||||
|
return _state.wormhole;
|
||||||
|
}
|
||||||
|
|
||||||
|
function portal() public view returns (ITokenBridge) {
|
||||||
|
return _state.portal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function finality(bool isFast) public view returns (uint8) {
|
||||||
|
return isFast ? _state.fastFinality : _state.finality;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity >=0.8.0 <0.9.0;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
|
||||||
|
|
||||||
|
import "./FastTransfer.sol";
|
||||||
|
|
||||||
|
contract FastTransferImplementation is FastTransfer, ERC1967Upgrade {
|
||||||
|
function initialize() initializer public virtual {
|
||||||
|
// this function needs to be exposed for an upgrade to pass
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier initializer() {
|
||||||
|
address impl = ERC1967Upgrade._getImplementation();
|
||||||
|
|
||||||
|
require(
|
||||||
|
!isInitialized(impl),
|
||||||
|
"already initialized"
|
||||||
|
);
|
||||||
|
|
||||||
|
setInitialized(impl);
|
||||||
|
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity >=0.8.0 <0.9.0;
|
||||||
|
|
||||||
|
import "./libraries/external/BytesLib.sol";
|
||||||
|
|
||||||
|
import "./FastTransferGetters.sol";
|
||||||
|
|
||||||
|
contract FastTransferMessages is FastTransferGetters {
|
||||||
|
function encodeFastTransfer(ITokenBridge.Transfer memory transfer) public view returns (bytes memory) {
|
||||||
|
return portal().encodeTransfer(transfer);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity >=0.8.0 <0.9.0;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||||
|
|
||||||
|
contract FastTransferProxy is ERC1967Proxy {
|
||||||
|
constructor (address implementation, bytes memory initData)
|
||||||
|
ERC1967Proxy(
|
||||||
|
implementation,
|
||||||
|
initData
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity >=0.8.0 <0.9.0;
|
||||||
|
|
||||||
|
import "./interfaces/ITokenBridge.sol";
|
||||||
|
|
||||||
|
import "./FastTransferState.sol";
|
||||||
|
|
||||||
|
contract FastTransferSetters is FastTransferState {
|
||||||
|
function setOwner(address owner) internal {
|
||||||
|
_state.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInitialized(address implementatiom) internal {
|
||||||
|
_state.initializedImplementations[implementatiom] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setChainId(uint16 chainId) internal {
|
||||||
|
_state.chainId = chainId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWormhole(address wormholeAddress) internal {
|
||||||
|
_state.wormhole = IWormhole(payable(wormholeAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPortal(address portalAddress) internal {
|
||||||
|
_state.portal = ITokenBridge(payable(portalAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFinality(uint8 finality_) internal {
|
||||||
|
_state.finality = finality_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFastFinality(uint8 fastFinality_) internal {
|
||||||
|
_state.fastFinality = fastFinality_;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity >=0.8.0 <0.9.0;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
|
||||||
|
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
|
||||||
|
|
||||||
|
import "./FastTransferSetters.sol";
|
||||||
|
|
||||||
|
contract FastTransferSetup is FastTransferSetters, ERC1967Upgrade, Context {
|
||||||
|
function setup(
|
||||||
|
address implementation,
|
||||||
|
uint16 chainId,
|
||||||
|
address wormhole,
|
||||||
|
address portal,
|
||||||
|
uint8 finality
|
||||||
|
) public {
|
||||||
|
require(wormhole != address(0), "invalid wormhole address");
|
||||||
|
require(portal != address(0), "invalid portal address");
|
||||||
|
require(implementation != address(0), "invalid implementation");
|
||||||
|
|
||||||
|
setOwner(_msgSender());
|
||||||
|
|
||||||
|
setChainId(chainId);
|
||||||
|
|
||||||
|
setWormhole(wormhole);
|
||||||
|
|
||||||
|
setPortal(portal);
|
||||||
|
|
||||||
|
setFinality(finality);
|
||||||
|
|
||||||
|
// fast finality is always 200
|
||||||
|
setFastFinality(200);
|
||||||
|
|
||||||
|
_upgradeTo(implementation);
|
||||||
|
|
||||||
|
/// @dev call initialize function of the new implementation
|
||||||
|
(bool success, bytes memory reason) = implementation.delegatecall(abi.encodeWithSignature("initialize()"));
|
||||||
|
require(success, string(reason));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity >=0.8.0 <0.9.0;
|
||||||
|
|
||||||
|
import "./interfaces/ITokenBridge.sol";
|
||||||
|
|
||||||
|
contract FastTransferStorage {
|
||||||
|
struct State {
|
||||||
|
// address of contract owner
|
||||||
|
address owner;
|
||||||
|
|
||||||
|
// chainId of this contract
|
||||||
|
uint16 chainId;
|
||||||
|
|
||||||
|
// wormhole message finality
|
||||||
|
uint8 finality;
|
||||||
|
|
||||||
|
// portal finality for fast transfers
|
||||||
|
uint8 fastFinality;
|
||||||
|
|
||||||
|
// portal instance
|
||||||
|
ITokenBridge portal;
|
||||||
|
|
||||||
|
// wormhole instance
|
||||||
|
IWormhole wormhole;
|
||||||
|
|
||||||
|
/// mapping of initialized implementations
|
||||||
|
mapping(address => bool) initializedImplementations;
|
||||||
|
|
||||||
|
// storage gap
|
||||||
|
uint256[50] ______gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract FastTransferState {
|
||||||
|
FastTransferStorage.State _state;
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
// contracts/Bridge.sol
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "./IWETH.sol";
|
||||||
|
import "./IWormhole.sol";
|
||||||
|
|
||||||
|
interface ITokenBridge {
|
||||||
|
struct Transfer {
|
||||||
|
uint8 payloadID;
|
||||||
|
uint256 amount;
|
||||||
|
bytes32 tokenAddress;
|
||||||
|
uint16 tokenChain;
|
||||||
|
bytes32 to;
|
||||||
|
uint16 toChain;
|
||||||
|
uint256 fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TransferWithPayload {
|
||||||
|
uint8 payloadID;
|
||||||
|
uint256 amount;
|
||||||
|
bytes32 tokenAddress;
|
||||||
|
uint16 tokenChain;
|
||||||
|
bytes32 to;
|
||||||
|
uint16 toChain;
|
||||||
|
bytes32 fromAddress;
|
||||||
|
bytes payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AssetMeta {
|
||||||
|
uint8 payloadID;
|
||||||
|
bytes32 tokenAddress;
|
||||||
|
uint16 tokenChain;
|
||||||
|
uint8 decimals;
|
||||||
|
bytes32 symbol;
|
||||||
|
bytes32 name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function attestToken(address tokenAddress, uint32 nonce) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
function wrapAndTransferETH(uint16 recipientChain, bytes32 recipient, uint256 arbiterFee, uint32 nonce) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
function wrapAndTransferETHWithPayload(uint16 recipientChain, bytes32 recipient, uint32 nonce, bytes memory payload) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
function transferTokens(address token, uint256 amount, uint16 recipientChain, bytes32 recipient, uint256 arbiterFee, uint32 nonce) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
function transferTokensWithPayload(address token, uint256 amount, uint16 recipientChain, bytes32 recipient, uint32 nonce, bytes memory payload) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
function updateWrapped(bytes memory encodedVm) external returns (address token);
|
||||||
|
|
||||||
|
function createWrapped(bytes memory encodedVm) external returns (address token);
|
||||||
|
|
||||||
|
function completeTransferWithPayload(bytes memory encodedVm) external returns (bytes memory);
|
||||||
|
|
||||||
|
function completeTransferAndUnwrapETHWithPayload(bytes memory encodedVm) external returns (bytes memory);
|
||||||
|
|
||||||
|
function completeTransfer(bytes memory encodedVm) external;
|
||||||
|
|
||||||
|
function completeTransferAndUnwrapETH(bytes memory encodedVm) external;
|
||||||
|
|
||||||
|
function encodeAssetMeta(AssetMeta memory meta) external pure returns (bytes memory encoded);
|
||||||
|
|
||||||
|
function encodeTransfer(Transfer memory transfer) external pure returns (bytes memory encoded);
|
||||||
|
|
||||||
|
function encodeTransferWithPayload(TransferWithPayload memory transfer) external pure returns (bytes memory encoded);
|
||||||
|
|
||||||
|
function parsePayloadID(bytes memory encoded) external pure returns (uint8 payloadID);
|
||||||
|
|
||||||
|
function parseAssetMeta(bytes memory encoded) external pure returns (AssetMeta memory meta);
|
||||||
|
|
||||||
|
function parseTransfer(bytes memory encoded) external pure returns (Transfer memory transfer);
|
||||||
|
|
||||||
|
function parseTransferWithPayload(bytes memory encoded) external pure returns (TransferWithPayload memory transfer);
|
||||||
|
|
||||||
|
function isTransferCompleted(bytes32 hash) external view returns (bool);
|
||||||
|
|
||||||
|
function wormhole() external view returns (IWormhole);
|
||||||
|
|
||||||
|
function chainId() external view returns (uint16);
|
||||||
|
|
||||||
|
function evmChainId() external view returns (uint256);
|
||||||
|
|
||||||
|
function wrappedAsset(uint16 tokenChainId, bytes32 tokenAddress) external view returns (address);
|
||||||
|
|
||||||
|
function WETH() external view returns (IWETH);
|
||||||
|
|
||||||
|
function outstandingBridged(address token) external view returns (uint256);
|
||||||
|
|
||||||
|
function isWrappedAsset(address token) external view returns (bool);
|
||||||
|
|
||||||
|
function finality() external view returns (uint8);
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// contracts/Bridge.sol
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
|
||||||
|
interface IWETH is IERC20 {
|
||||||
|
function deposit() external payable;
|
||||||
|
function withdraw(uint amount) external;
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// contracts/Messages.sol
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
interface IWormhole {
|
||||||
|
struct GuardianSet {
|
||||||
|
address[] keys;
|
||||||
|
uint32 expirationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Signature {
|
||||||
|
bytes32 r;
|
||||||
|
bytes32 s;
|
||||||
|
uint8 v;
|
||||||
|
uint8 guardianIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VM {
|
||||||
|
uint8 version;
|
||||||
|
uint32 timestamp;
|
||||||
|
uint32 nonce;
|
||||||
|
uint16 emitterChainId;
|
||||||
|
bytes32 emitterAddress;
|
||||||
|
uint64 sequence;
|
||||||
|
uint8 consistencyLevel;
|
||||||
|
bytes payload;
|
||||||
|
|
||||||
|
uint32 guardianSetIndex;
|
||||||
|
Signature[] signatures;
|
||||||
|
|
||||||
|
bytes32 hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);
|
||||||
|
|
||||||
|
function publishMessage(
|
||||||
|
uint32 nonce,
|
||||||
|
bytes memory payload,
|
||||||
|
uint8 consistencyLevel
|
||||||
|
) external payable returns (uint64 sequence);
|
||||||
|
|
||||||
|
function parseAndVerifyVM(bytes calldata encodedVM) external view returns (VM memory vm, bool valid, string memory reason);
|
||||||
|
|
||||||
|
function verifyVM(VM memory vm) external view returns (bool valid, string memory reason);
|
||||||
|
|
||||||
|
function verifySignatures(bytes32 hash, Signature[] memory signatures, GuardianSet memory guardianSet) external pure returns (bool valid, string memory reason);
|
||||||
|
|
||||||
|
function parseVM(bytes memory encodedVM) external pure returns (VM memory vm);
|
||||||
|
|
||||||
|
function getGuardianSet(uint32 index) external view returns (GuardianSet memory);
|
||||||
|
|
||||||
|
function getCurrentGuardianSetIndex() external view returns (uint32);
|
||||||
|
|
||||||
|
function getGuardianSetExpiry() external view returns (uint32);
|
||||||
|
|
||||||
|
function governanceActionIsConsumed(bytes32 hash) external view returns (bool);
|
||||||
|
|
||||||
|
function isInitialized(address impl) external view returns (bool);
|
||||||
|
|
||||||
|
function chainId() external view returns (uint16);
|
||||||
|
|
||||||
|
function governanceChainId() external view returns (uint16);
|
||||||
|
|
||||||
|
function governanceContract() external view returns (bytes32);
|
||||||
|
|
||||||
|
function messageFee() external view returns (uint256);
|
||||||
|
|
||||||
|
function evmChainId() external view returns (uint256);
|
||||||
|
|
||||||
|
function nextSequence(address emitter) external view returns (uint64);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// SPDX-License-Identifier: Apache 2
|
||||||
|
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import "forge-std/Test.sol";
|
||||||
|
import "forge-std/console.sol";
|
||||||
|
|
||||||
|
import "../contracts/FastTransfer.sol";
|
||||||
|
|
||||||
|
contract TestFastTransfer is Test {
|
||||||
|
using BytesLib for bytes;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
[profile.default]
|
||||||
|
src = 'contracts'
|
||||||
|
out = 'build'
|
||||||
|
test = 'forge-test'
|
||||||
|
|
||||||
|
libs = [
|
||||||
|
"lib",
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
|
||||||
|
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
|
Loading…
Reference in New Issue