Skip to content

Commit

Permalink
Merge pull request #14 from warpdotgreen/actions
Browse files Browse the repository at this point in the history
Actions + NatSpec
  • Loading branch information
Yakuhito authored May 7, 2024
2 parents 59558d3 + dddc8a5 commit 20ba7fd
Show file tree
Hide file tree
Showing 6 changed files with 370 additions and 16 deletions.
68 changes: 68 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Run Tests

on:
push:
branches:
- master

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Clean workspace
uses: Chia-Network/actions/clean-workspace@main

- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0

- uses: Chia-Network/actions/setup-python@main
with:
python-version: '3.11'

- name: Create 'test-plots' Directory
run: mkdir -p ~/.chia/test-plots

- uses: Chia-Network/actions/create-venv@main
id: create-venv

- uses: Chia-Network/actions/activate-venv@main
with:
directories: ${{ steps.create-venv.outputs.activate-venv-directories }}

- name: Install Python dependencies
run: |
python3 -m pip install --upgrade pip
pip install --extra-index-url https://pypi.chia.net/simple/ chia-dev-tools==1.2.5
pip install -r requirements.txt
- name: Run pytest
run: |
pytest tests/ -s -v --durations 0 -W ignore::DeprecationWarning
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '18'

- name: Install npm dependencies
run: npm install

- name: Run Hardhat tests
run: |
cp hardhat.config.example.ts hardhat.config.ts
npx hardhat test
- name: Check test coverage for Solidity contracts
run: |
COVERAGE=$(npx hardhat coverage)
SECOND_PART=$(echo "$COVERAGE" | awk -F 'contracts/ |' '{print $2}')
PARTS=$(echo "$SECOND_PART" | cut -d '|' -f 2-5)
if [ "$(echo "$PARTS" | tr -d '[:space:]')" = "100|100|100|100" ]; then
echo "Test coverage is 100% for the contracts/*.sol"
else
echo "Test coverage is less than 100% for contracts/*.sol"
exit 1
fi
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ On Chia, messages are picked up by looking for the following output condition:
docker build . -t cli
echo '{}' > config.json
touch data.db
docker run -v "$(pwd)"/config.json:/app/config.json -v "$(pwd)"/data.db:/app/data.db cli --help
docker run -it -v "$(pwd)"/config.json:/app/config.json -v "$(pwd)"/data.db:/app/data.db cli --help
```

3. Ensure prerequisite software is installed. This repo has been tested with `python 3.10/3.11` and `nodejs v18`. If you have a different node version, uninstall and install the correct version via:
Expand Down
107 changes: 99 additions & 8 deletions contracts/ERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,63 @@ interface ERC20Decimals {
function decimals() external returns (uint8);
}

/**
* @title ERC-20 Bridge for the warp.green protocol
* @notice Allows wrapping ERC-20 tokens on Chia and unwrapping them back to the original network using warp.green.
* @dev Not compatible with fee-on-transfer or rebasing tokens. The contract assumes 1 token now is 1 token in the future, and that transferring `a` tokens will result to exactly `a` tokens being granted to the recipient (not more, not less).
*/
contract ERC20Bridge is IPortalMessageReceiver {
uint16 public immutable tip; // (tip / 10000) % tip
/**
* @notice When wrapping or unwrapping ERC-20s, a tip is sent to the warp.green protocol. The tip is expressed in basis points.
* @dev Tip is calculated as amount * tip / 10000; the remaining amount is sent to the recipient.
*/
uint16 public immutable tip;

/**
* @notice The address of the warp.green portal contract.
* @dev Used as an oracle. The portal usually sits behind a TransparentUpgradeableProxy.
*/
address public immutable portal;

/**
* @notice The address of the contract used to convert between ether and an equivalent ERC-20. Usually milliETH or WETH.
* @dev WETH uses a conversion rate of 1:1, while milliETH uses one of 1000 milliETH = 1 ETH.
*/
address public immutable iweth;
uint64 public immutable wethToEthRatio; // in wei - how much wei one 'wei' of WETH translates to
// for example: 1000 milliETH = 1 ETH, so 10^(3+3) wei milliETH (3 decimals) translates to 10^18 wei -> ratio is 10^12
// amount_weth * wethToEthRatio = eth amount to pay out

/**
* @notice How much wei the smallest unit of the wrapped ether ERC-20 translates to.
* @dev For example, 1000 milliETH = 1 ETH, so 10^(3+3) wei milliETH (milliETH has 3 decimals) translates to 10^18 wei. The ratio would be 10^12, as 0.001 milliETH (smallest denomination) translates to 10^12 wei = 0.000001 ETH.
*/
uint64 public immutable wethToEthRatio;

/**
* @notice The chain id of the other chain. Usually "xch", which indicates Chia.
* @dev Used to verify the source chain of messages.
*/
bytes3 public immutable otherChain;

/**
* @notice The hash of the puzzle used to burn CATs associated with an ERC-20 that was locked in this contract. Sender of messages from Chia.
* @dev Used to verify the source of messages when they're received.
*/
bytes32 public burnPuzzleHash;

/**
* @notice The hash of the puzzle used to mint CATs on Chia after they've been transferred to this contract. Message receiver on Chia.
* @dev Used as a destination for sent messages.
*/
bytes32 public mintPuzzleHash;

/**
* @notice ERC20Bridge constructor
* @param _tip The percentage (in basis points) used as a tip for the warp.green protocol
* @param _portal Address of the warp.green portal contract
* @param _iweth Address of the WETH contract or its equivalent
* @param _wethToEthRatio The conversion ratio from 1 'wei' of WETH to ETH, considering the difference in decimals
* @param _otherChain The chain ID of the destination chain, typically 'xch' for Chia
* @dev Initializes contract state with immutable values for gas efficiency
*/
constructor(
uint16 _tip,
address _portal,
Expand All @@ -40,10 +85,12 @@ contract ERC20Bridge is IPortalMessageReceiver {
otherChain = _otherChain;
}

// should be called in the same tx/block as the creation tx
// allows the address to be determined using CREATE2
// w/o depending on puzzle hashes (since those already depend
// on the address of this contract)
/**
* @notice Initialize the contract. Should be called in the same transaction as the deployment.
* @dev Allows the address of the contract to be determined using CREATE2, as the arguments below depend on the address of this contract. Can only be called once per contract lifetime.
* @param _burnPuzzleHash Chia-side burn puzzle hash. Will be used to set the value of `burnPuzzleHash`.
* @param _mintPuzzleHash Chia-side mint puzzle hash. Will be used to set the value of `mintPuzzleHash`.
*/
function initializePuzzleHashes(
bytes32 _burnPuzzleHash,
bytes32 _mintPuzzleHash
Expand All @@ -56,6 +103,13 @@ contract ERC20Bridge is IPortalMessageReceiver {
mintPuzzleHash = _mintPuzzleHash;
}

/**
* @notice Receives and processes messages from the warp.green portal
* @dev Uses the warp.green Portal contract as an oracle; verifies message and handles the unwrapping process.
* @param _source_chain Message source chain id (e.g., "xch").
* @param _source Message source (puzzle hash).
* @param _contents Message contents - asset contract, receiver, and mojo amount.
*/
function receiveMessage(
bytes32 /* _nonce */,
bytes3 _source_chain,
Expand Down Expand Up @@ -92,6 +146,13 @@ contract ERC20Bridge is IPortalMessageReceiver {
}
}

/**
* @notice Bridges ERC-20 tokens to Chia via the warp.green protocol
* @dev Transfers tokens to this contract, redirects portal tip, and send message for tokens to be minted on Chia.
* @param _assetContract Address of the ERC-20 token to bridge.
* @param _receiver Receiver of the wrapped tokens on Chia, given as a puzzle hash. Usually the decoded bech32m address taken from the wallet.
* @param _mojoAmount Amount to bridge to Chia, in mojos (1 CAT = 10^3 mojos). For example, 1 would mean 0.001 CAT tokens, equivalent to 0.001 ERC-20 tokens.
*/
function bridgeToChia(
address _assetContract,
bytes32 _receiver,
Expand All @@ -109,6 +170,11 @@ contract ERC20Bridge is IPortalMessageReceiver {
);
}

/**
* @notice Bridges native Ether to Chia by first wrapping it into milliETH (or WETH).
* @dev Wraps ether into an ERC-20, sends portal tip, and sends a message to mint tokens on Chia.
* @param _receiver Receiver puzzle hash for the wrapped tokens.
*/
function bridgeEtherToChia(bytes32 _receiver) external payable {
uint256 messageToll = IPortal(portal).messageToll();

Expand All @@ -133,6 +199,17 @@ contract ERC20Bridge is IPortalMessageReceiver {
);
}

/**
* @notice Bridges ERC-20 tokens to Chia with a permit allowing token spend
* @dev Uses ERC-20 permit for gas-efficient token approval and transfer in a single transaction.
* @param _assetContract Address of the ERC-20 token to bridge.
* @param _receiver Receiver puzzle hash for the wrapped tokens.
* @param _amount Amount to bridge to Chia, in mojos. For example, 1 would mean 0.001 CAT tokens, equivalent to 0.001 ERC-20 tokens.
* @param _deadline Permit deadline.
* @param _v Permit signature v value.
* @param _r Permit signature r value.
* @param _s Permit signature s value.
*/
function bridgeToChiaWithPermit(
address _assetContract,
bytes32 _receiver,
Expand Down Expand Up @@ -165,6 +242,16 @@ contract ERC20Bridge is IPortalMessageReceiver {
);
}

/**
* @notice Internal function used to craft and send message acknowledging tokens were locked on this chain.
* @dev Tip is also calculated and transferred to the portal in this function.
* @param _assetContract Address of the ERC-20 token to bridge.
* @param _transferAsset Whether to transfer the asset to this contract or not. If false, the tokens must already be owned by this contract.
* @param _receiver Receiver puzzle hash for the wrapped tokens.
* @param _amount Amount of CAT tokens to be minted on Chia (in mojos).
* @param _messageToll Message toll in wei required by the portal to relay the message.
* @param _mojoToTokenFactor A power of 10 to convert from CAT amount (mojos) to the ERC-20 token's smallest unit.
*/
function _handleBridging(
address _assetContract,
bool _transferAsset,
Expand Down Expand Up @@ -201,6 +288,10 @@ contract ERC20Bridge is IPortalMessageReceiver {
);
}

/**
* @notice Function that handles incoming ether transfers. Do not simply send ether to this contract.
* @dev The 'require' should prevent user errors.
*/
receive() external payable {
require(msg.sender == iweth, "!sender");
}
Expand Down
27 changes: 27 additions & 0 deletions contracts/MilliETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,53 @@ pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./interfaces/IWETH.sol";

/**
* @title milliETH Token Contract
* @notice milliETH is an ERC-20 token, where 1 milliETH is equivalent to 1/1000th of one ether.
* @dev No fees or tips. The token only has 3 decimals.
*/
contract MilliETH is ERC20, IWETH {
/**
* @notice MilliETH constructor
* @dev We opted against using "mETH" as the symbol.
*/
constructor() ERC20("milliETH", "milliETH") {}

/**
* @notice Returns milliETH's number of decimals, which is 3.
* @dev Overrides the decimals method to set milliETH decimals to 3.
* @return decimals The number of decimals for milliETH tokens.
*/
function decimals() public pure override returns (uint8) {
return 3;
}

/**
* @notice Used to mint milliETH equivalent to the deposited ether value.
* @dev Accepts ETH, mints corresponding milliETH tokens considering the conversion rate of 1000 milliETH per ETH. Requires the deposited ETH to be divisible by 1e12 wei for correct conversion.
*/
function deposit() public payable {
// msg.value will have 18 decimals; we want 6 (1000 milliETH:1 ETH ratio + 3 decimals for 1 milliETH token)
require(msg.value > 0 && msg.value % 1e12 == 0, "!msg.value");
_mint(msg.sender, msg.value / 1e12);
}

/**
* @notice Called to withdraw milliETH. Equivalent ether value is sent back.
* @dev Burns the specified amount of milliETH and sends the equivalent amount of ETH back to the sender.
* @param amount The amount of milliETH to burn and convert to ether.
*/
function withdraw(uint256 amount) external {
require(amount > 0, "!amount");
_burn(msg.sender, amount);
(bool sent, ) = msg.sender.call{value: amount * 1e12}("");
require(sent, "!sent");
}

/**
* @notice Receives ether and automatically deposits it, minting milliETH tokens to the sender.
* @dev Allows the contract to accept direct ETH transfers and handles them by calling the deposit function.
*/
receive() external payable {
deposit();
}
Expand Down
Loading

0 comments on commit 20ba7fd

Please sign in to comment.