Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Foundry toolkit that enables Sapphire precompile and decryption #484

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 0 additions & 1 deletion .github/workflows/ci-playwright.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,3 @@ jobs:
with:
name: playwright-test-results
path: examples/wagmi-v2/test-results
retention-days: 5
58 changes: 58 additions & 0 deletions .github/workflows/ci-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,61 @@ jobs:
python3 -mpip install --user -r requirements.dev.txt
python3 -munittest discover
pytest sapphirepy/tests/

test-examples-foundry:
name: test-examples-foundry
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./examples/foundry
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install libclang-dev
run: sudo apt-get update && sudo apt-get install -y libclang-dev
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Install Rustup
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
- name: Install Rust nightly
run: rustup toolchain install nightly
- name: Install CMake
uses: lukka/get-cmake@latest

- name: Install dependencies
run: make build

- name: Run tests
run: forge test

test-integrations-foundry:
name: test-integrations-foundry
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./integrations/foundry
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install libclang-dev
run: sudo apt-get update && sudo apt-get install -y libclang-dev
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Install Rustup
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
- name: Install Rust nightly
run: rustup toolchain install nightly
- name: Install CMake
uses: lukka/get-cmake@latest

- name: Install dependencies
run: make build

- name: Run tests
run: forge test
1 change: 0 additions & 1 deletion .github/workflows/contracts-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,3 @@ jobs:
run: cargo install mdbook-pagetoc
- name: Build docs
working-directory: contracts
run: pnpm doc
2 changes: 1 addition & 1 deletion examples/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
NPM ?= pnpm

SUBDIRS := $(wildcard */.) # e.g. "foo/. bar/."
SUBDIRS := $(filter-out foundry/.,$(wildcard */.) ) # all dirs except foundry
TARGETS := all clean lint test build distclean # whatever else, but must not contain '/'

# foo/.all bar/.all foo/.clean bar/.clean
Expand Down
17 changes: 17 additions & 0 deletions examples/foundry/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

all: build test

build:
forge install foundry-rs/forge-std --no-commit --no-git
forge soldeer install @oasisprotocol-sapphire-contracts~0.2.11 --config-location foundry
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we simply put a dependencies/oasisprotocol-sapphire-contracts symlink pointing to ../../../contracts folder instead? This way we could always test whether sapphire contracts broke any foundry example specifics.

cd src/precompiles && cargo +nightly build --release

test:
forge test

clean:
forge clean
rm -rf cache build lib src/precompiles/Cargo.lock src/precompiles/target

.PHONY: all build test clean

35 changes: 35 additions & 0 deletions examples/foundry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Foundry example for Sapphire developers

This project showcases Sapphire development with Foundry.


## Overview

Test folder contains a set of Forge tests that access precompiles
and demonstrate sapphire-specific functionality.
Makefile contains build/test/clean targets.

## Installation
1. **Requirements**
- Foundry:
- `curl -L https://foundry.paradigm.xyz | bash`
- `foundryup`
- Rust (nightly):
- `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`
- `rustup toolchain install nightly`
- `rustup default nightly`
- Make

2. **Install dependencies**
- Run `make build` ( Using foundry.toml ) to install dependencies and build rust bindings.

## Run tests

Steps to run:
- Make sure you're in the `sapphire-paratime/examples/foundry` directory
- Run forge tests `forge test -vvv`

### A note on fuzz tests:
Check out [fuzzing docs](https://book.getfoundry.sh/forge/fuzz-testing)


27 changes: 27 additions & 0 deletions examples/foundry/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib', "dependencies"]
ffi = true
memory_limit = 1000000000

[soldeer]
# whether soldeer manages remappings
remappings_generate = true

# whether soldeer re-generates all remappings when installing, updating or uninstalling deps
remappings_regenerate = false

# whether to suffix the remapping with the version: `name-a.b.c`
remappings_version = true

# a prefix to add to the remappings ("@" would give `@name`)
remappings_prefix = ""

# where to store the remappings ("txt" for `remappings.txt` or "config" for `foundry.toml`)
# ignored when `soldeer.toml` is used as config (uses `remappings.txt`)
remappings_location = "txt"




1 change: 1 addition & 0 deletions examples/foundry/src
117 changes: 117 additions & 0 deletions examples/foundry/test/TestSapphireContracts.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
import "../src/BaseSapphireTest.sol";

contract Safe {
receive() external payable {}

function withdraw() external {
payable(msg.sender).transfer(address(this).balance);
}
}

contract CalldataEncryptionTest is SapphireTest {
Safe safe;

receive() external payable {}

function setUp() public override {
super.setUp(); // Call parent setUp first
safe = new Safe();
}

/*
Fuzz tests for the Safe contract.
*/
function testFuzz_Withdraw(uint96 amount) public {
console.log("Contract balance: ", address(this).balance);
payable(address(safe)).transfer(amount);
uint256 preBalance = address(this).balance;
safe.withdraw();
uint256 postBalance = address(this).balance;
assertEq(preBalance + amount, postBalance);
}


/*
Test for the CalldataEncryption contract
which is used to encrypt and decrypt calldata.
*/
function testEncryptCallData() public {
bytes memory in_data = bytes("Hello, Sapphire!");

Sapphire.Curve25519PublicKey myPublic;
Sapphire.Curve25519SecretKey mySecret;

(myPublic, mySecret) = Sapphire.generateCurve25519KeyPair("");

bytes15 nonce = bytes15(Sapphire.randomBytes(15, ""));

Subcall.CallDataPublicKey memory cdpk;
uint256 epoch;

(epoch, cdpk) = Subcall.coreCallDataPublicKey();
bytes memory result = testCalldataEncryption.testEncryptCallData(
in_data,
myPublic,
mySecret,
nonce,
epoch,
cdpk.key
);
(bool success, bytes memory decrypted) = address(bytes20(keccak256(bytes("0x987654321098765432109876543210")))).call(abi.encode(result));
assertEq(success, true);
assertEq(decrypted, in_data);
}

/*
Fuzz tests for the CalldataEncryption contract.
Foundry automatically generates nonces for fuzz tests.
*/
function testEncryptCallDataFuzz(bytes15 nonce) public {
bytes memory in_data = bytes("Hello, Sapphire!");

Sapphire.Curve25519PublicKey myPublic;
Sapphire.Curve25519SecretKey mySecret;

(myPublic, mySecret) = Sapphire.generateCurve25519KeyPair("");

// bytes15 nonce = bytes15(Sapphire.randomBytes(15, ""));

Subcall.CallDataPublicKey memory cdpk;
uint256 epoch;

(epoch, cdpk) = Subcall.coreCallDataPublicKey();
bytes memory result = testCalldataEncryption.testEncryptCallData(
in_data,
myPublic,
mySecret,
nonce,
epoch,
cdpk.key
);
(bool success, bytes memory decrypted) = address(bytes20(keccak256(bytes("0x987654321098765432109876543210")))).call(abi.encode(result));
assertEq(success, true);
assertEq(decrypted, in_data);
}

/*
Test incrementing the counter using Decryptor Base contract,
which is inherited from the BaseSapphireTest contract.
*/
function testCounterEncryptCallData() public {
bytes memory encryptedData = encryptCallData(abi.encodeWithSelector(counter.increment.selector));
(bool success, bytes memory decryptedData) = address(bytes20(keccak256(bytes("0x987654321098765432109876543210")))).call(abi.encode(encryptedData));
assertEq(success, true);
assertEq(decryptedData, abi.encodeWithSelector(counter.increment.selector));

console.log("Counter number: ", counter.number());
uint256 initialNumber = counter.number();
(success, decryptedData) = address(counter).call(encryptedData);
assertEq(success, true);
assertEq(counter.number(), initialNumber + 1);
}
}
2 changes: 1 addition & 1 deletion integrations/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
NPM ?= pnpm

SUBDIRS := $(wildcard */.) # e.g. "foo/. bar/."
SUBDIRS := $(filter-out foundry/.,$(wildcard */.) ) # all dirs except foundry
TARGETS := all clean lint test build distclean # whatever else, but must not contain '/'

# foo/.all bar/.all foo/.clean bar/.clean
Expand Down
16 changes: 16 additions & 0 deletions integrations/foundry/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

all: build test

build:
forge install foundry-rs/forge-std --no-commit --no-git
forge soldeer install @oasisprotocol-sapphire-contracts~0.2.11 --config-location foundry
cd src/precompiles && cargo +nightly build --release

test:
forge test

clean:
forge clean
rm -rf cache build lib src/precompiles/Cargo.lock src/precompiles/target

.PHONY: all build test clean
59 changes: 59 additions & 0 deletions integrations/foundry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Foundry Package for Sapphire developers

This project implements Sapphire-compatible precompiles
for use with forge tests. It includes support for
confidential computing capabilities through encryption/decryption handling.

## Overview

This project contains a set of contracts that mock Sapphire's precompile
behavior using rust bindings. In addition, it contains SapphireDecryptor,
a contract that can decrypt encrypted calldata. SapphireDecryptor works by
inheritence and will forward the decrypted calldata to the contract that
called it. This is useful, for example, to test the execution of the on-chain
generated gasless transactions with encrypted calldata.

## List of precompiles

### Cryptographic Operations
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line after the title should always be empty.

Suggested change
### Cryptographic Operations
### Cryptographic Operations

- `RandomBytes`: Generate random bytes
- `X25519Derive`: Derive shared secrets using X25519
- `DeoxysiiSeal`: Encrypt data using Deoxys-II
- `DeoxysiiOpen`: Decrypt data using Deoxys-II
- `Curve25519ComputePublic`: Compute public keys

### Key Management
- `KeypairGenerate`: Generate cryptographic keypairs
- `Sign`: Sign messages
- `Verify`: Verify signatures

### Consensus Operations
- `Subcall`: Enhanced version with CBOR parsing and state management for:
- Delegations
- Undelegations
- Receipt tracking
Comment on lines +38 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Delegations
- Undelegations
- Receipt tracking
- Delegations
- Undelegations
- Receipt tracking


## Key Features

1. **Sapphire precompiles as contracts**
- Can run as native precompiles
- Easy import into forge tests

2. **Decryption Base contract**
- Enables decryption at contract level
- Used as a base contract for other contracts that implement encryption

## Installation

This folder contains precompile contracts and rust bindings that can be
imported into separate Foundry projects.
To install the dependencies, run `make build` ( Using foundry.toml ).


## Usage
To test the precompiles, run `forge test`.

For a test example, see [sapphire-paratime/examples/foundry].
[sapphire-paratime/examples/foundry]: https://github.com/oasisprotocol/sapphire-paratime/tree/main/examples/foundry


28 changes: 28 additions & 0 deletions integrations/foundry/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib', "dependencies"]
ffi = true
memory_limit = 1000000000

[soldeer]
# whether soldeer manages remappings
remappings_generate = true

# whether soldeer re-generates all remappings when installing, updating or uninstalling deps
remappings_regenerate = false

# whether to suffix the remapping with the version: `name-a.b.c`
remappings_version = true

# a prefix to add to the remappings ("@" would give `@name`)
remappings_prefix = ""

# where to store the remappings ("txt" for `remappings.txt` or "config" for `foundry.toml`)
# ignored when `soldeer.toml` is used as config (uses `remappings.txt`)
remappings_location = "txt"





Loading
Loading