mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-11-03 17:31:11 +00:00
ci: Add Foundry tooling integration test (#511)
## Description Adds a CI workflow that builds and tests a small Foundry project with `foundry-polkadot`'s `forge`, exercising the standard JSON interface between `resolc` and `foundry-polkadot` end-to-end. ## Resolved Issues Closes #513
This commit is contained in:
10 files changed
+239
-12
No files matched your search
@@ -0,0 +1,75 @@
|
|||||||
|
# Tests compilation and E2E integration with foundry-polkadot, including basic
|
||||||
|
# runtime behavior, but not differential runtime correctness.
|
||||||
|
|
||||||
|
name: Foundry Integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
# Workflow-internal foundry envs are prefixed with `CI_` to not conflict with the
|
||||||
|
# `FOUNDRY_*` namespace, which foundry itself will interpret for its configuration.
|
||||||
|
CI_FOUNDRY_PROJECT_ROOT: tooling-projects/foundry/erc20
|
||||||
|
CI_FOUNDRY_POLKADOT_VERSION: v1.5.1
|
||||||
|
# Coincides with the forge-std revision pinned in foundry-polkadot/testdata/forge-std-rev.
|
||||||
|
FORGE_STD_VERSION: v1.16.1
|
||||||
|
OZ_CONTRACTS_VERSION: v5.3.0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
uses: ./.github/workflows/reusable-build-linux.yml
|
||||||
|
with:
|
||||||
|
retention_days: 1
|
||||||
|
|
||||||
|
use-foundry:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout revive
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Download resolc Binary
|
||||||
|
uses: actions/download-artifact@v7
|
||||||
|
with:
|
||||||
|
name: resolc-x86_64-unknown-linux-musl
|
||||||
|
path: resolc-bin
|
||||||
|
|
||||||
|
- name: Export resolc Path
|
||||||
|
run: |
|
||||||
|
chmod +x resolc-bin/resolc-x86_64-unknown-linux-musl
|
||||||
|
echo "RESOLC_PATH=$(pwd)/resolc-bin/resolc-x86_64-unknown-linux-musl" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install foundryup-polkadot and the Foundry Toolchain
|
||||||
|
run: |
|
||||||
|
# Pin install path with `FOUNDRY_DIR` to prevent the pre-existing `XDG_CONFIG_HOME` from being used.
|
||||||
|
export FOUNDRY_DIR="$HOME/.foundry"
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/paritytech/foundry-polkadot/refs/heads/master/foundryup/install | bash
|
||||||
|
"$FOUNDRY_DIR/bin/foundryup-polkadot" --install "$CI_FOUNDRY_POLKADOT_VERSION"
|
||||||
|
|
||||||
|
- name: Add Foundry to PATH
|
||||||
|
run: echo "$HOME/.foundry/bin" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
|
- name: Check Tool Versions
|
||||||
|
run: |
|
||||||
|
foundryup-polkadot --version
|
||||||
|
forge --version
|
||||||
|
|
||||||
|
- name: Fetch Project Dependencies
|
||||||
|
working-directory: ${{ env.CI_FOUNDRY_PROJECT_ROOT }}
|
||||||
|
run: |
|
||||||
|
mkdir -p lib
|
||||||
|
git clone --depth 1 --branch "$FORGE_STD_VERSION" https://github.com/foundry-rs/forge-std.git lib/forge-std
|
||||||
|
git clone --depth 1 --branch "$OZ_CONTRACTS_VERSION" https://github.com/OpenZeppelin/openzeppelin-contracts.git lib/openzeppelin-contracts
|
||||||
|
|
||||||
|
- name: Compile Project
|
||||||
|
working-directory: ${{ env.CI_FOUNDRY_PROJECT_ROOT }}
|
||||||
|
run: forge build --use-resolc "$RESOLC_PATH" --optimize -Oz
|
||||||
|
|
||||||
|
- name: Verify Output
|
||||||
|
working-directory: ${{ env.CI_FOUNDRY_PROJECT_ROOT }}
|
||||||
|
run: bash verify-compiler-output.sh "$RESOLC_PATH"
|
||||||
|
|
||||||
|
- name: Test Project
|
||||||
|
working-directory: ${{ env.CI_FOUNDRY_PROJECT_ROOT }}
|
||||||
|
run: forge test --polkadot=pvm --use-resolc "$RESOLC_PATH" --optimize -Oz -vvvv
|
||||||
@@ -28,6 +28,7 @@ jobs:
|
|||||||
# Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group.
|
# Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group.
|
||||||
group: reusable-build-x86_64-unknown-linux-musl-${{ github.sha }}
|
group: reusable-build-x86_64-unknown-linux-musl-${{ github.sha }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
queue: max
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout revive
|
- name: Checkout revive
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ jobs:
|
|||||||
# Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group.
|
# Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group.
|
||||||
group: reusable-build-${{ matrix.target }}-${{ github.sha }}
|
group: reusable-build-${{ matrix.target }}-${{ github.sha }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
queue: max
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout revive
|
- name: Checkout revive
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
@@ -80,6 +81,7 @@ jobs:
|
|||||||
# Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group.
|
# Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group.
|
||||||
group: reusable-build-universal-apple-darwin-${{ github.sha }}
|
group: reusable-build-universal-apple-darwin-${{ github.sha }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
queue: max
|
||||||
steps:
|
steps:
|
||||||
- name: Check Build Cache
|
- name: Check Build Cache
|
||||||
id: cache
|
id: cache
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ jobs:
|
|||||||
# Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group.
|
# Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group.
|
||||||
group: reusable-build-wasm32-unknown-emscripten-is-release-${{ inputs.is_release }}-${{ github.sha }}
|
group: reusable-build-wasm32-unknown-emscripten-is-release-${{ inputs.is_release }}-${{ github.sha }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
queue: max
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout revive
|
- name: Checkout revive
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ jobs:
|
|||||||
# Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group.
|
# Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group.
|
||||||
group: reusable-build-x86_64-pc-windows-msvc-${{ github.sha }}
|
group: reusable-build-x86_64-pc-windows-msvc-${{ github.sha }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
queue: max
|
||||||
steps:
|
steps:
|
||||||
- name: Enable Long Paths
|
- name: Enable Long Paths
|
||||||
run: git config --system core.longpaths true
|
run: git config --system core.longpaths true
|
||||||
|
|||||||
+23
-12
@@ -64,6 +64,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should-run-differential-tests: ${{ steps.filter.outputs.should-run-differential-tests }}
|
should-run-differential-tests: ${{ steps.filter.outputs.should-run-differential-tests }}
|
||||||
should-run-hardhat-tests: ${{ steps.filter.outputs.should-run-hardhat-tests }}
|
should-run-hardhat-tests: ${{ steps.filter.outputs.should-run-hardhat-tests }}
|
||||||
|
should-run-foundry-tests: ${{ steps.filter.outputs.should-run-foundry-tests }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
@@ -95,23 +96,28 @@ jobs:
|
|||||||
|
|
||||||
print_changed_files
|
print_changed_files
|
||||||
|
|
||||||
|
common_ignored=(
|
||||||
|
'\.md$'
|
||||||
|
'^book/'
|
||||||
|
'^docs/'
|
||||||
|
'^js/'
|
||||||
|
'^package\.json$'
|
||||||
|
'^package-lock\.json$'
|
||||||
|
)
|
||||||
|
|
||||||
echo "should-run-differential-tests=$(has_changes_other_than \
|
echo "should-run-differential-tests=$(has_changes_other_than \
|
||||||
'\.md$' \
|
"${common_ignored[@]}" \
|
||||||
'^book/' \
|
|
||||||
'^docs/' \
|
|
||||||
'^js/' \
|
|
||||||
'^package\.json$' \
|
|
||||||
'^package-lock\.json$' \
|
|
||||||
'^tooling-projects/' \
|
'^tooling-projects/' \
|
||||||
)" | tee -a "$GITHUB_OUTPUT"
|
)" | tee -a "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
echo "should-run-hardhat-tests=$(has_changes_other_than \
|
echo "should-run-hardhat-tests=$(has_changes_other_than \
|
||||||
'\.md$' \
|
"${common_ignored[@]}" \
|
||||||
'^book/' \
|
'^tooling-projects/foundry' \
|
||||||
'^docs/' \
|
)" | tee -a "$GITHUB_OUTPUT"
|
||||||
'^js/' \
|
|
||||||
'^package\.json$' \
|
echo "should-run-foundry-tests=$(has_changes_other_than \
|
||||||
'^package-lock\.json$' \
|
"${common_ignored[@]}" \
|
||||||
|
'^tooling-projects/hardhat' \
|
||||||
)" | tee -a "$GITHUB_OUTPUT"
|
)" | tee -a "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
run-differential-tests:
|
run-differential-tests:
|
||||||
@@ -123,3 +129,8 @@ jobs:
|
|||||||
needs: [test, check-changes]
|
needs: [test, check-changes]
|
||||||
if: ${{ needs.check-changes.outputs.should-run-hardhat-tests == 'true' }}
|
if: ${{ needs.check-changes.outputs.should-run-hardhat-tests == 'true' }}
|
||||||
uses: ./.github/workflows/hardhat.yml
|
uses: ./.github/workflows/hardhat.yml
|
||||||
|
|
||||||
|
run-foundry-tests:
|
||||||
|
needs: [test, check-changes]
|
||||||
|
if: ${{ needs.check-changes.outputs.should-run-foundry-tests == 'true' }}
|
||||||
|
uses: ./.github/workflows/foundry.yml
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
[profile.default]
|
||||||
|
src = "src"
|
||||||
|
test = "test"
|
||||||
|
libs = ["lib"]
|
||||||
|
solc = "0.8.35"
|
||||||
|
remappings = [
|
||||||
|
"forge-std/=lib/forge-std/src/",
|
||||||
|
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
|
||||||
|
]
|
||||||
|
extra_output = [
|
||||||
|
"abi",
|
||||||
|
"metadata",
|
||||||
|
"devdoc",
|
||||||
|
"userdoc",
|
||||||
|
"storageLayout",
|
||||||
|
"ir",
|
||||||
|
"irOptimized",
|
||||||
|
"evm.bytecode",
|
||||||
|
"evm.deployedBytecode",
|
||||||
|
"evm.assembly",
|
||||||
|
"evm.methodIdentifiers",
|
||||||
|
]
|
||||||
|
|
||||||
|
[profile.default.polkadot]
|
||||||
|
resolc_compile = true
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Compatible with OpenZeppelin Contracts ^5.0.0
|
||||||
|
pragma solidity ^0.8.22;
|
||||||
|
|
||||||
|
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
|
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
|
||||||
|
|
||||||
|
contract MyToken is ERC20, Ownable {
|
||||||
|
constructor(address initialOwner)
|
||||||
|
ERC20("MyToken", "MTK")
|
||||||
|
Ownable(initialOwner)
|
||||||
|
{}
|
||||||
|
|
||||||
|
function mint(address to, uint256 amount) public onlyOwner {
|
||||||
|
_mint(to, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.22;
|
||||||
|
|
||||||
|
import { Test } from "forge-std/Test.sol";
|
||||||
|
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
|
||||||
|
import { MyToken } from "../src/MyToken.sol";
|
||||||
|
|
||||||
|
contract MyTokenTest is Test {
|
||||||
|
MyToken internal token;
|
||||||
|
address internal owner = address(this);
|
||||||
|
address internal alice = address(0xA11CE);
|
||||||
|
address internal bob = address(0xB0B);
|
||||||
|
|
||||||
|
function setUp() public {
|
||||||
|
token = new MyToken(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_NameAndSymbol() public view {
|
||||||
|
assertEq(token.name(), "MyToken");
|
||||||
|
assertEq(token.symbol(), "MTK");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_Owner() public view {
|
||||||
|
assertEq(token.owner(), owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_OwnerCanMint() public {
|
||||||
|
token.mint(alice, 1000);
|
||||||
|
assertEq(token.balanceOf(alice), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_NonOwnerCannotMint() public {
|
||||||
|
vm.prank(alice);
|
||||||
|
vm.expectRevert(
|
||||||
|
abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice)
|
||||||
|
);
|
||||||
|
token.mint(alice, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_TotalSupplyIncreases() public {
|
||||||
|
uint256 before = token.totalSupply();
|
||||||
|
token.mint(alice, 500);
|
||||||
|
assertEq(token.totalSupply() - before, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_Transfer() public {
|
||||||
|
token.mint(owner, 1000);
|
||||||
|
assertTrue(token.transfer(alice, 100));
|
||||||
|
assertEq(token.balanceOf(alice), 100);
|
||||||
|
assertEq(token.balanceOf(owner), 900);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_TransferFrom() public {
|
||||||
|
token.mint(owner, 1000);
|
||||||
|
token.approve(alice, 50);
|
||||||
|
vm.prank(alice);
|
||||||
|
assertTrue(token.transferFrom(owner, bob, 50));
|
||||||
|
assertEq(token.balanceOf(bob), 50);
|
||||||
|
assertEq(token.allowance(owner, alice), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Asserts that the compiled project contains the expected compiler output.
|
||||||
|
# Requires `forge` in PATH. Run from the project's root.
|
||||||
|
#
|
||||||
|
# Usage: verify-compiler-output.sh <resolc-path>
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "usage: $(basename "$0") <resolc-path>" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolc=$1
|
||||||
|
|
||||||
|
inspect() {
|
||||||
|
forge inspect --use-resolc "$resolc" MyToken "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
inspect bytecode | grep '^0x50564d' > /dev/null
|
||||||
|
inspect deployedBytecode | grep '^0x50564d' > /dev/null
|
||||||
|
inspect irOptimized | grep . > /dev/null
|
||||||
|
inspect ir | grep . > /dev/null
|
||||||
|
inspect assembly | grep . > /dev/null
|
||||||
|
inspect abi --json | jq -e 'length > 0' > /dev/null
|
||||||
|
inspect methodIdentifiers --json | jq -e 'length > 0' > /dev/null
|
||||||
|
inspect storageLayout --json | jq -e '.storage | length > 0' > /dev/null
|
||||||
|
inspect metadata --json | jq -e 'length > 0' > /dev/null
|
||||||
|
inspect devdoc --json | jq -e 'length > 0' > /dev/null
|
||||||
|
inspect userdoc --json | jq -e 'length > 0' > /dev/null
|
||||||
|
|
||||||
|
echo "all checks passed"
|
||||||
Reference in new issue
Block a user