Skip to content

Commit

Permalink
Implement precompile mock contracts and foundry mock util test contra…
Browse files Browse the repository at this point in the history
…cts (#462)

- Add foundry test support files
- Add husky commit-msg to help devs not forget signing off
- Add foundry testing readme file
- Add npm package script for easy runs of foundry, hardhat and localnode
- Add HTS mock and test contracts and use for reference foundry tests
- Add github CI action to run foundry tests

---------

Signed-off-by: Mo Shaikjee <[email protected]>
  • Loading branch information
mshakeg authored Oct 17, 2023
1 parent 6000257 commit b7f9d1e
Show file tree
Hide file tree
Showing 32 changed files with 4,779 additions and 4 deletions.
46 changes: 46 additions & 0 deletions .github/workflows/foundry_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Foundry Tests

on:
workflow_dispatch:
pull_request:
branches: [main]

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive

- name: Use Node.js [18.15]
uses: actions/setup-node@v3
with:
node-version: 18.15
cache: npm

- name: Create .env file
run: cp local.env .env

- name: Install dependencies
run: npm ci

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Run Forge build
run: |
forge --version
forge build
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
5 changes: 5 additions & 0 deletions .github/workflows/test-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Start the local node
run: npx hedera start -d --network local
timeout-minutes: 5
Expand Down
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,24 @@ artifacts/contracts
.env
test-results.*

## --- Foundry Gitignore ---

# Compiler files
forge-cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Coverage
lcov.info
coverage/

## --- Hardhat Gitignore for Foundry artifacts ---
artifacts/forge-std
artifacts/ds-test
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
4 changes: 4 additions & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

grep -q "Signed-off-by" $1 || (echo "No Signed-off-by found. Run git commit --signoff" && false)
66 changes: 66 additions & 0 deletions FOUNDRY_TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ For further details on methods, hashes and availability please refer to [PRNG Pr

This project is set up using the Hardhat development environment. To get started, please follow this [test setup guide](./TEST_SETUP.md).

For using this project as a library in a Foundry project see [Foundry Testing](FOUNDRY_TESTING.md)

## Support

If you have a question on how to use the product, please see our
Expand All @@ -63,4 +65,3 @@ to [[email protected]](mailto:[email protected]).
## Smart contracts - testing

[Smart contracts tests - documentation](https://raw.githubusercontent.com/hashgraph/hedera-smart-contracts/main/test/README.md)

31 changes: 31 additions & 0 deletions contracts/base/NoDelegateCall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;

import "../libraries/Constants.sol";

/// @title Prevents delegatecall to a contract
/// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract
abstract contract NoDelegateCall is Constants {
/// @dev The original address of this contract
address private immutable original;

/// @dev slightly modified as in context of constructor address(this) is the address of the deployed contract and not the etched contract address
/// hence _original allows passing the address to which a contract is etched to; for normal uses pass ADDRESS_ZERO
constructor(address _original) {
// Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
// In other words, this variable won't change when it's checked at runtime.
original = _original == ADDRESS_ZERO ? address(this) : _original;
}

/// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
/// and the use of immutable means the address bytes are copied in every place the modifier is used.
function checkNotDelegateCall() private view {
require(address(this) == original, "NO_DELEGATECALL");
}

/// @notice Prevents delegatecall into the modified method
modifier noDelegateCall() {
checkNotDelegateCall();
_;
}
}
10 changes: 10 additions & 0 deletions contracts/libraries/Constants.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.9;

abstract contract Constants {
address internal constant HTS_PRECOMPILE = address(0x167);
address internal constant EXCHANGE_RATE_PRECOMPILE = address(0x168);
address internal constant UTIL_PRECOMPILE = address(0x168);

address internal constant ADDRESS_ZERO = address(0);
}
12 changes: 12 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[profile.default]
src = 'contracts'
out = 'out'
libs = ['node_modules', 'lib']
test = 'test/foundry'
cache_path = 'forge-cache'
remappings = [
'@openzeppelin/=node_modules/@openzeppelin/',
'hardhat/=node_modules/hardhat/',
]

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
1 change: 1 addition & 0 deletions hardhat.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require('@nomicfoundation/hardhat-chai-matchers')
require('@nomiclabs/hardhat-ethers')
require('@openzeppelin/hardhat-upgrades')
require('@nomicfoundation/hardhat-foundry');
const {
OPERATOR_ID_A,
OPERATOR_KEY_A,
Expand Down
1 change: 1 addition & 0 deletions lib/forge-std
Submodule forge-std added at f73c73
31 changes: 30 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 15 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,28 @@
"url": "https://github.com/hashgraph/hedera-smart-contracts/issues"
},
"homepage": "https://github.com/hashgraph/hedera-smart-contracts#readme",
"scripts": {
"forge:build": "forge build",
"forge:test": "forge test",
"forge:coverage": "forge coverage",
"forge:coverage:report": "forge coverage --report lcov",
"forge:coverage:html": "forge coverage --report lcov && genhtml lcov.info --branch-coverage --output-dir coverage",
"hh:compile": "hardhat compile",
"hh:test": "hardhat test",
"hedera:start": "npx @hashgraph/hedera-local start --limits=false --dev=true --balance=10000000",
"hedera:stop": "npx @hashgraph/hedera-local stop",
"prepare": "husky install"
},
"devDependencies": {
"@hashgraph/hedera-local": "2.13.0",
"@hashgraph/sdk": "^2.25.0",
"@nomicfoundation/hardhat-chai-matchers": "^1.0.6",
"@nomicfoundation/hardhat-foundry": "^1.1.1",
"@openzeppelin/contracts": "^4.9.3",
"@openzeppelin/contracts-upgradeable": "^4.9.3",
"@openzeppelin/hardhat-upgrades": "^1.22.1",
"hardhat": "^2.14.0",
"hardhat": "^2.17.2",
"husky": "^8.0.0",
"mocha-junit-reporter": "^2.2.0",
"mocha-multi-reporters": "^1.5.1",
"prettier": "3.0.0"
Expand All @@ -33,4 +47,3 @@
"dotenv": "^16.3.1"
}
}

2 changes: 2 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
28 changes: 28 additions & 0 deletions test/foundry/ExchangeRatePrecompileMock.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.9;

import './utils/ExchangeRateUtils.sol';

contract ExchangeRatePrecompileMockTest is ExchangeRateUtils {

// setUp is executed before each and every test function
function setUp() public {
_setUpExchangeRatePrecompileMock();
_setUpAccounts();
}

function test_CanCorrectlyConvertTinycentsToTinybars() public {
uint256 tinycents = 1e8;
uint256 tinybars = _doConvertTinycentsToTinybars(tinycents);
assertEq(tinybars, 1e7, "expected 1 cent to equal 1e7 tinybar(0.1 HBAR) at $0.1/HBAR");
}

function test_CanCorrectlyConvertTinybarsToTinyCents() public {
uint256 tinybars = 1e8;
uint256 tinycents = _doConvertTinybarsToTinycents(tinybars);
assertEq(tinycents, 1e9, "expected 1 HBAR to equal 10 cents(1e9 tinycents) at $0.1/HBAR");
}

}

// forge test --match-contract ExchangeRatePrecompileMockTest -vv
Loading

0 comments on commit b7f9d1e

Please sign in to comment.