Skip to content

Commit

Permalink
Merge pull request #119 from sablier-labs/feat/precompiles
Browse files Browse the repository at this point in the history
feat: precompiles
  • Loading branch information
PaulRBerg authored Jul 1, 2023
2 parents 8178922 + 3ba0921 commit 925e7f7
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 8 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,33 @@ jobs:
echo "## Integration tests result" >> $GITHUB_STEP_SUMMARY
echo "✅ Passed" >> $GITHUB_STEP_SUMMARY
test-utils:
needs: ["lint", "build"]
runs-on: "ubuntu-latest"
steps:
- name: "Check out the repo"
uses: "actions/checkout@v3"
with:
submodules: "recursive"

- name: "Install Foundry"
uses: "foundry-rs/foundry-toolchain@v1"

- name: "Restore the cached build"
uses: "actions/cache/restore@v3"
with:
fail-on-cache-miss: true
key: "foundry-build-${{ github.sha }}"
path: "out-optimized"

- name: "Run the utils tests against the optimized build"
run: "FOUNDRY_PROFILE=test-optimized forge test --match-path \"test/utils/**/*.sol\""

- name: "Add test summary"
run: |
echo "## Utils tests result" >> $GITHUB_STEP_SUMMARY
echo "✅ Passed" >> $GITHUB_STEP_SUMMARY
test-fork:
needs: ["lint", "build"]
runs-on: "ubuntu-latest"
Expand Down
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
branch = "release-v0"
path = "lib/prb-test"
url = "https://github.com/PaulRBerg/prb-test"
[submodule "lib/solady"]
branch = "main"
path = "lib/solady"
url = "https://github.com/Vectorized/solady"
[submodule "lib/v2-core"]
branch = "main"
path = "lib/v2-core"
Expand Down
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at 7175c2
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
forge-std/=lib/forge-std/src/
permit2/=lib/permit2/src/
permit2-test/=lib/permit2/test/
solady/=lib/solady/src/
31 changes: 31 additions & 0 deletions shell/update-precompiles.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash

# Pre-requisites:
# - foundry (https://getfoundry.sh)
# - jq (https://stedolan.github.io/jq)
# - sd (https://github.com/chmln/sd)

# Strict mode: https://gist.github.com/vncsna/64825d5609c146e80de8b1fd623011ca
set -euo pipefail

# Compile the contracts with Forge
FOUNDRY_PROFILE=optimized forge build

# Retrieve the raw bytecodes, removing the "0x" prefix
archive=$(cat out-optimized/SablierV2Archive.sol/SablierV2Archive.json | jq -r '.bytecode.object' | cut -c 3-)
proxy_plugin=$(cat out-optimized/SablierV2ProxyPlugin.sol/SablierV2ProxyPlugin.json | jq -r '.bytecode.object' | cut -c 3-)
proxy_target=$(cat out-optimized/SablierV2ProxyTarget.sol/SablierV2ProxyTarget.json | jq -r '.bytecode.object' | cut -c 3-)

precompiles_path="test/utils/Precompiles.sol"
if [ ! -f $precompiles_path ]; then
echo "Precompiles file does not exist"
exit 1
fi

# Replace the current bytecodes
sd "(BYTECODE_ARCHIVE =)[^;]+;" "\$1 hex\"$archive\";" $precompiles_path
sd "(BYTECODE_PROXY_PLUGIN =)[^;]+;" "\$1 hex\"$proxy_plugin\";" $precompiles_path
sd "(BYTECODE_PROXY_TARGET =)[^;]+;" "\$1 hex\"$proxy_target\";" $precompiles_path

# Reformat the code with Forge
forge fmt $precompiles_path
13 changes: 9 additions & 4 deletions test/Base.t.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19 <0.9.0;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { IPRBProxy } from "@prb/proxy/interfaces/IPRBProxy.sol";
Expand Down Expand Up @@ -58,6 +59,10 @@ abstract contract Base_Test is Assertions, Events, StdCheats, V2CoreUtils {
//////////////////////////////////////////////////////////////////////////*/

function setUp() public virtual {
// Deploy the default test asset.
asset = new ERC20("DAI Stablecoin", "DAI");

// Create users for testing.
users.alice = createUser("Alice");
users.admin = createUser("Admin");
users.broker = createUser("Broker");
Expand All @@ -84,8 +89,8 @@ abstract contract Base_Test is Assertions, Events, StdCheats, V2CoreUtils {
target = new SablierV2ProxyTarget(permit2);
} else {
archive = deployPrecompiledArchive(users.admin.addr);
plugin = deployPrecompiledPlugin(archive);
target = deployPrecompiledTarget(permit2);
plugin = deployPrecompiledProxyPlugin(archive);
target = deployPrecompiledProxyTarget(permit2);
}
}

Expand All @@ -97,14 +102,14 @@ abstract contract Base_Test is Assertions, Events, StdCheats, V2CoreUtils {
}

/// @dev Deploys {SablierV2ProxyPlugin} from a source precompiled with `--via-ir`.
function deployPrecompiledPlugin(ISablierV2Archive archive_) internal returns (ISablierV2ProxyPlugin) {
function deployPrecompiledProxyPlugin(ISablierV2Archive archive_) internal returns (ISablierV2ProxyPlugin) {
return ISablierV2ProxyPlugin(
deployCode("out-optimized/SablierV2ProxyPlugin.sol/SablierV2ProxyPlugin.json", abi.encode(archive_))
);
}

/// @dev Deploys {SablierV2ProxyTarget} from a source precompiled with `--via-ir`.
function deployPrecompiledTarget(IAllowanceTransfer permit2_) internal returns (ISablierV2ProxyTarget) {
function deployPrecompiledProxyTarget(IAllowanceTransfer permit2_) internal returns (ISablierV2ProxyTarget) {
return ISablierV2ProxyTarget(
deployCode("out-optimized/SablierV2ProxyTarget.sol/SablierV2ProxyTarget.json", abi.encode(permit2_))
);
Expand Down
4 changes: 0 additions & 4 deletions test/integration/Integration.t.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19 <0.9.0;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IAllowanceTransfer } from "permit2/interfaces/IAllowanceTransfer.sol";

import { Precompiles as PRBProxyPrecompiles } from "@prb/proxy-test/utils/Precompiles.sol";
Expand All @@ -20,9 +19,6 @@ abstract contract Integration_Test is Base_Test {
//////////////////////////////////////////////////////////////////////////*/

function setUp() public virtual override {
// Deploy the default test asset.
asset = new ERC20("DAI Stablecoin", "DAI");

// Set up the base test contract.
Base_Test.setUp();

Expand Down
74 changes: 74 additions & 0 deletions test/utils/Precompiles.sol

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions test/utils/Precompiles.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19 <=0.9.0;

import { IAllowanceTransfer } from "permit2/interfaces/IAllowanceTransfer.sol";
import { DeployPermit2 } from "permit2-test/utils/DeployPermit2.sol";
import { LibString } from "solady/utils/LibString.sol";

import { ISablierV2Archive } from "../../src/interfaces/ISablierV2Archive.sol";
import { ISablierV2ProxyPlugin } from "../../src/interfaces/ISablierV2ProxyPlugin.sol";
import { ISablierV2ProxyTarget } from "../../src/interfaces/ISablierV2ProxyTarget.sol";

import { Base_Test } from "../Base.t.sol";
import { Precompiles } from "./Precompiles.sol";

contract Precompiles_Test is Base_Test {
using LibString for address;
using LibString for string;

Precompiles internal precompiles = new Precompiles();

modifier onlyTestOptimizedProfile() {
if (isTestOptimizedProfile()) {
_;
}
}

function test_DeployArchive() external onlyTestOptimizedProfile {
address actualArchive = address(precompiles.deployArchive(users.admin.addr));
address expectedArchive = address(deployPrecompiledArchive(users.admin.addr));
assertEq(actualArchive.code, expectedArchive.code, "bytecodes mismatch");
}

function test_DeployProxyPlugin() external onlyTestOptimizedProfile {
ISablierV2Archive archive = deployPrecompiledArchive(users.admin.addr);
address actualProxyPlugin = address(precompiles.deployProxyPlugin(archive));
address expectedProxyPlugin = address(deployPrecompiledProxyPlugin(archive));
bytes memory expectedProxyPluginCode =
adjustBytecode(expectedProxyPlugin.code, expectedProxyPlugin, actualProxyPlugin);
assertEq(actualProxyPlugin.code, expectedProxyPluginCode, "bytecodes mismatch");
}

function test_DeployProxyTarget() external onlyTestOptimizedProfile {
IAllowanceTransfer permit2 = IAllowanceTransfer(new DeployPermit2().run());
address actualProxyTarget = address(precompiles.deployProxyTarget(permit2));
address expectedProxyTarget = address(deployPrecompiledProxyTarget(permit2));
bytes memory expectedProxyTargetCode =
adjustBytecode(expectedProxyTarget.code, expectedProxyTarget, actualProxyTarget);
assertEq(actualProxyTarget.code, expectedProxyTargetCode, "bytecodes mismatch");
}

function test_DeployPeriphery() external onlyTestOptimizedProfile {
IAllowanceTransfer permit2 = IAllowanceTransfer(new DeployPermit2().run());
(
ISablierV2Archive actualArchive,
ISablierV2ProxyPlugin actualProxyPlugin,
ISablierV2ProxyTarget actualProxyTarget
) = precompiles.deployPeriphery(users.admin.addr, permit2);

address expectedArchive = address(deployPrecompiledArchive(users.admin.addr));
assertEq(address(actualArchive).code, expectedArchive.code, "bytecodes mismatch");

address expectedProxyPlugin = address(deployPrecompiledProxyPlugin(actualArchive));
bytes memory expectedLockupDynamicCode =
adjustBytecode(expectedProxyPlugin.code, expectedProxyPlugin, address(actualProxyPlugin));
assertEq(address(actualProxyPlugin).code, expectedLockupDynamicCode, "bytecodes mismatch");

address expectedProxyTarget = address(deployPrecompiledProxyTarget(permit2));
bytes memory expectedProxyTargetCode =
adjustBytecode(expectedProxyTarget.code, expectedProxyTarget, address(actualProxyTarget));
assertEq(address(actualProxyTarget).code, expectedProxyTargetCode, "bytecodes mismatch");
}

/// @dev The expected bytecode has to be adjusted because some contracts inherit from {OnlyDelegateCall}, which
/// saves the contract's own address in storage.
function adjustBytecode(
bytes memory bytecode,
address expectedAddress,
address actualAddress
)
internal
pure
returns (bytes memory)
{
return vm.parseBytes(
vm.toString(bytecode).replace({
search: expectedAddress.toHexStringNoPrefix(),
replacement: actualAddress.toHexStringNoPrefix()
})
);
}
}

0 comments on commit 925e7f7

Please sign in to comment.