├── .cargo └── config.toml ├── .config └── nextest.toml ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── codecov.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── check-abi.yml │ ├── check-antora.yml │ ├── check-md.yml │ ├── check-package.yml │ ├── check-wasm.yml │ ├── check.yml │ ├── e2e-tests.yml │ ├── fuzz.yml │ ├── gas-bench.yml │ ├── publish-openzeppelin-crypto.yml │ ├── publish-openzeppelin-stylus-proc.yml │ ├── publish-openzeppelin-stylus.yml │ └── test.yml ├── .gitignore ├── .linkspector.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── GUIDELINES.md ├── LICENSE ├── README.md ├── SECURITY.md ├── audits └── 2024-10-v0.1.0.pdf ├── benches ├── Cargo.toml └── src │ ├── access_control.rs │ ├── erc1155.rs │ ├── erc1155_metadata_uri.rs │ ├── erc1155_supply.rs │ ├── erc20.rs │ ├── erc721.rs │ ├── lib.rs │ ├── main.rs │ ├── merkle_proofs.rs │ ├── ownable.rs │ ├── pedersen.rs │ ├── poseidon.rs │ ├── poseidon_asm_sol.rs │ ├── poseidon_sol.rs │ ├── report.rs │ └── vesting_wallet.rs ├── clippy.toml ├── contracts-proc ├── Cargo.toml ├── README.md └── src │ ├── interface_id.rs │ └── lib.rs ├── contracts ├── Cargo.toml ├── README.md ├── proptest-regressions │ └── utils │ │ └── math │ │ └── alloy.txt └── src │ ├── access │ ├── control │ │ ├── extensions │ │ │ ├── enumerable.rs │ │ │ └── mod.rs │ │ └── mod.rs │ ├── mod.rs │ ├── ownable.rs │ └── ownable_two_step.rs │ ├── finance │ ├── mod.rs │ └── vesting_wallet.rs │ ├── lib.rs │ ├── token │ ├── common │ │ ├── erc2981.rs │ │ └── mod.rs │ ├── erc1155 │ │ ├── extensions │ │ │ ├── burnable.rs │ │ │ ├── metadata_uri.rs │ │ │ ├── mod.rs │ │ │ ├── supply.rs │ │ │ └── uri_storage.rs │ │ ├── mod.rs │ │ └── receiver.rs │ ├── erc20 │ │ ├── extensions │ │ │ ├── burnable.rs │ │ │ ├── capped.rs │ │ │ ├── erc4626.rs │ │ │ ├── flash_mint.rs │ │ │ ├── metadata.rs │ │ │ ├── mod.rs │ │ │ ├── permit.rs │ │ │ └── wrapper.rs │ │ ├── interface.rs │ │ ├── mod.rs │ │ └── utils │ │ │ ├── mod.rs │ │ │ └── safe_erc20.rs │ ├── erc721 │ │ ├── extensions │ │ │ ├── burnable.rs │ │ │ ├── consecutive.rs │ │ │ ├── enumerable.rs │ │ │ ├── metadata.rs │ │ │ ├── mod.rs │ │ │ ├── uri_storage.rs │ │ │ └── wrapper.rs │ │ ├── interface.rs │ │ ├── mod.rs │ │ └── receiver.rs │ └── mod.rs │ └── utils │ ├── cryptography │ ├── ecdsa.rs │ ├── eip712.rs │ └── mod.rs │ ├── introspection │ ├── erc165.rs │ └── mod.rs │ ├── math │ ├── alloy.rs │ ├── mod.rs │ └── storage │ │ ├── checked.rs │ │ ├── mod.rs │ │ └── unchecked.rs │ ├── metadata.rs │ ├── mod.rs │ ├── nonces.rs │ ├── pausable.rs │ └── structs │ ├── bitmap.rs │ ├── checkpoints │ ├── generic_size.rs │ └── mod.rs │ ├── enumerable_set.rs │ └── mod.rs ├── docs ├── antora.yml ├── modules │ └── ROOT │ │ ├── nav.adoc │ │ └── pages │ │ ├── access-control.adoc │ │ ├── common.adoc │ │ ├── crypto.adoc │ │ ├── erc1155-burnable.adoc │ │ ├── erc1155-metadata-uri.adoc │ │ ├── erc1155-pausable.adoc │ │ ├── erc1155-supply.adoc │ │ ├── erc1155-uri-storage.adoc │ │ ├── erc1155.adoc │ │ ├── erc20-burnable.adoc │ │ ├── erc20-capped.adoc │ │ ├── erc20-flash-mint.adoc │ │ ├── erc20-metadata.adoc │ │ ├── erc20-pausable.adoc │ │ ├── erc20-permit.adoc │ │ ├── erc20-wrapper.adoc │ │ ├── erc20.adoc │ │ ├── erc2981.adoc │ │ ├── erc4626.adoc │ │ ├── erc721-burnable.adoc │ │ ├── erc721-consecutive.adoc │ │ ├── erc721-enumerable.adoc │ │ ├── erc721-metadata.adoc │ │ ├── erc721-pausable.adoc │ │ ├── erc721-uri-storage.adoc │ │ ├── erc721-wrapper.adoc │ │ ├── erc721.adoc │ │ ├── finance.adoc │ │ ├── index.adoc │ │ ├── tokens.adoc │ │ ├── utilities.adoc │ │ └── vesting-wallet.adoc ├── package-lock.json └── package.json ├── examples ├── access-control │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── access_control.rs ├── basic │ ├── README.md │ ├── script │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── token │ │ ├── Cargo.toml │ │ └── src │ │ ├── lib.rs │ │ └── main.rs ├── ecdsa │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── ecdsa.rs ├── erc1155-metadata-uri │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── erc1155-metadata-uri.rs ├── erc1155-supply │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ ├── erc1155-supply.rs │ │ └── mock │ │ ├── mod.rs │ │ └── receiver.rs ├── erc1155 │ ├── Cargo.toml │ ├── src │ │ ├── ERC1155ReceiverMock.sol │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ ├── erc1155.rs │ │ └── mock │ │ ├── mod.rs │ │ └── receiver.rs ├── erc20-flash-mint │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ ├── erc20_flash_mint.rs │ │ └── mock │ │ ├── borrower.rs │ │ └── mod.rs ├── erc20-permit │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── erc20_permit.rs ├── erc20-wrapper │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ ├── erc20_wrapper.rs │ │ └── mock │ │ ├── erc20.rs │ │ └── mod.rs ├── erc20 │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── erc20.rs ├── erc4626 │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ ├── erc4626.rs │ │ └── mock │ │ ├── erc20.rs │ │ ├── erc20_failing_transfer.rs │ │ └── mod.rs ├── erc721-consecutive │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── erc721_consecutive.rs ├── erc721-metadata │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── erc721_metadata.rs ├── erc721-wrapper │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ ├── erc721_wrapper.rs │ │ └── mock │ │ ├── erc721.rs │ │ └── mod.rs ├── erc721 │ ├── Cargo.toml │ ├── src │ │ ├── ERC721ReceiverMock.sol │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ ├── erc721.rs │ │ └── mock │ │ ├── mod.rs │ │ └── receiver.rs ├── merkle-proofs │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── main.rs ├── ownable-two-step │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── ownable_two_step.rs ├── ownable │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── ownable.rs ├── pedersen │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── pedersen.rs ├── poseidon │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ └── poseidon.rs ├── safe-erc20 │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── main.rs │ └── tests │ │ ├── abi │ │ └── mod.rs │ │ ├── mock │ │ ├── erc20.rs │ │ ├── erc20_force_approve.rs │ │ ├── erc20_no_return.rs │ │ ├── erc20_return_false.rs │ │ └── mod.rs │ │ ├── safe_erc20_address_with_no_code.rs │ │ ├── safe_erc20_erc20.rs │ │ ├── safe_erc20_erc20_that_always_returns_false.rs │ │ ├── safe_erc20_erc20_that_does_not_return.rs │ │ └── safe_erc20_usdt_approval_behavior.rs └── vesting-wallet │ ├── Cargo.toml │ ├── src │ ├── lib.rs │ └── main.rs │ └── tests │ ├── abi │ └── mod.rs │ ├── mock │ ├── erc20.rs │ ├── erc20_return_false.rs │ └── mod.rs │ └── vesting-wallet.rs ├── fuzz ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── fuzz_targets │ ├── keccak.rs │ └── merkle.rs └── src │ ├── lib.rs │ └── merkle │ └── corpus.rs ├── lib ├── crypto │ ├── Cargo.toml │ ├── README.md │ ├── proptest-regressions │ │ ├── field │ │ │ └── fp.txt │ │ └── merkle.txt │ └── src │ │ ├── arithmetic │ │ ├── limb.rs │ │ ├── mod.rs │ │ └── uint.rs │ │ ├── bits.rs │ │ ├── const_helpers.rs │ │ ├── curve │ │ ├── helpers.rs │ │ ├── mod.rs │ │ └── sw │ │ │ ├── affine.rs │ │ │ ├── mod.rs │ │ │ └── projective.rs │ │ ├── field │ │ ├── fp.rs │ │ ├── group.rs │ │ ├── instance.rs │ │ ├── mod.rs │ │ └── prime.rs │ │ ├── hash.rs │ │ ├── keccak.rs │ │ ├── lib.rs │ │ ├── merkle.rs │ │ ├── pedersen │ │ ├── instance │ │ │ ├── mod.rs │ │ │ └── starknet.rs │ │ ├── mod.rs │ │ └── params.rs │ │ ├── poseidon2 │ │ ├── instance │ │ │ ├── babybear.rs │ │ │ ├── bls12.rs │ │ │ ├── bn256.rs │ │ │ ├── goldilocks.rs │ │ │ ├── mod.rs │ │ │ ├── pallas.rs │ │ │ └── vesta.rs │ │ ├── mod.rs │ │ └── params.rs │ │ └── test_helpers.rs ├── e2e-proc │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ └── test.rs └── e2e │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── account.rs │ ├── constructor_macro.rs │ ├── deploy.rs │ ├── environment.rs │ ├── error.rs │ ├── event.rs │ ├── lib.rs │ ├── project.rs │ ├── receipt.rs │ └── system.rs ├── netlify.toml ├── rust-toolchain.toml ├── rustfmt.toml └── scripts ├── bench.sh ├── check-abi.sh ├── check-wasm.sh ├── e2e-tests.sh └── nitro-testnode.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-unknown] 2 | rustflags = [ 3 | "-C", 4 | "link-arg=-zstack-size=8192", 5 | "-C", 6 | "target-cpu=mvp", 7 | "-C", 8 | "target-feature=+bulk-memory", 9 | ] 10 | 11 | [target.aarch64-apple-darwin] 12 | rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] 13 | 14 | [target.x86_64-apple-darwin] 15 | rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] 16 | -------------------------------------------------------------------------------- /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | [profile.ci.junit] 2 | path = "junit.xml" 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @bidzyyys @qalisander @0xNeshi 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Create a report to help us improve 3 | title: "[Bug]: " 4 | labels: ["type: bug", "needs triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! Briefly describe the issue you're experiencing. Tell us what you were trying to do and what happened instead. Remember, this is not a place to ask for help debugging code. For that, we welcome you in the OpenZeppelin Community Forum: https://forum.openzeppelin.com/. 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: What happened? 14 | description: if you want, you can include screenshots as well :) 15 | validations: 16 | required: true 17 | - type: checkboxes 18 | id: platform 19 | attributes: 20 | label: platform 21 | description: On which operating system did this bug emerge? 22 | options: 23 | - label: linux 24 | required: false 25 | - label: windows 26 | required: false 27 | - label: macos 28 | required: false 29 | - type: textarea 30 | id: expected 31 | attributes: 32 | label: Expected behavior 33 | description: What should have happened instead? 34 | - type: checkboxes 35 | id: terms 36 | attributes: 37 | label: Contribution Guidelines 38 | description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/CONTRIBUTING.md) 39 | options: 40 | - label: I agree to follow this project's Contribution Guidelines 41 | required: true 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Questions & Support Requests 3 | url: https://forum.openzeppelin.com/c/support/17 4 | about: Ask in the OpenZeppelin Forum 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🎁 Feature Request 2 | description: Suggest an idea for this project ⚡️ 3 | title: "[Feature]: " 4 | labels: ["type: feature", "needs triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill this feature request! 10 | - type: textarea 11 | id: feature 12 | attributes: 13 | label: What is the feature you would like to see? 14 | description: Is your feature request related to a specific problem? Is it just a crazy idea? Tell us about it! 15 | validations: 16 | required: true 17 | - type: checkboxes 18 | id: terms 19 | attributes: 20 | label: Contribution Guidelines 21 | description: By submitting this issue, you agree to follow our [Contribution Guidelines](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/CONTRIBUTING.md) 22 | options: 23 | - label: I agree to follow this project's Contribution Guidelines 24 | required: true 25 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.codecov.com/docs/codecovyml-reference 2 | coverage: 3 | # Hold ourselves to a high bar. 4 | range: 95..100 5 | round: down 6 | precision: 1 7 | status: 8 | # ref: https://docs.codecov.com/docs/commit-status 9 | project: 10 | default: 11 | # Avoid false negatives. 12 | threshold: 1% 13 | informational: true 14 | patch: 15 | default: 16 | informational: true 17 | # Docs and examples are not relevant to coverage. 18 | ignore: 19 | - "docs" 20 | - "examples" 21 | - "lib/e2e" 22 | - "lib/e2e-proc" 23 | # Make comments less noisy. 24 | comment: 25 | layout: "files" 26 | require_changes: true 27 | github_checks: 28 | annotations: false 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: daily 11 | ignore: 12 | - dependency-name: "*" 13 | update-types: 14 | - "version-update:semver-patch" 15 | - "version-update:semver-minor" 16 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | Resolves #??? 11 | 12 | #### PR Checklist 13 | 14 | 19 | 20 | - [ ] Tests 21 | - [ ] Documentation 22 | - [ ] Changelog 23 | -------------------------------------------------------------------------------- /.github/workflows/check-abi.yml: -------------------------------------------------------------------------------- 1 | name: check-abi 2 | # This workflow checks that the example contracts can export their ABI. 3 | permissions: 4 | contents: read 5 | on: 6 | push: 7 | branches: [main, v*] 8 | pull_request: 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 11 | cancel-in-progress: true 12 | env: 13 | CARGO_TERM_COLOR: always 14 | jobs: 15 | check-abi: 16 | name: Check ABI 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Install rust 22 | uses: actions-rust-lang/setup-rust-toolchain@v1 23 | with: 24 | rustflags: "" 25 | 26 | - name: Install cargo-stylus 27 | run: cargo install cargo-stylus@0.6.0 28 | 29 | - name: Run export-abi 30 | run: ./scripts/check-abi.sh 31 | -------------------------------------------------------------------------------- /.github/workflows/check-antora.yml: -------------------------------------------------------------------------------- 1 | name: Check Antora Docs 2 | # This workflow checks that all links in the documentation are valid. 3 | # It does this for antora docs(adoc). 4 | on: workflow_dispatch 5 | 6 | jobs: 7 | check-links-adoc: 8 | name: Check AsciiDoc Links 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Run linkspector 14 | uses: umbrelladocs/action-linkspector@v1 15 | with: 16 | fail_on_error: true 17 | -------------------------------------------------------------------------------- /.github/workflows/check-md.yml: -------------------------------------------------------------------------------- 1 | name: Check Markdown 2 | # This workflow checks that all links in the documentation are valid. 3 | # It does this for markdown files. 4 | # We prefer lycheeverse because it is faster, but doesn't support adoc files yet(https://github.com/lycheeverse/lychee/issues/291) 5 | # Because of that, we use linkspector for adoc files and lychee for md files. 6 | on: 7 | push: 8 | branches: [main, v*] 9 | pull_request: 10 | branches: [main, v*] 11 | 12 | jobs: 13 | check-links-md: 14 | name: Check Markdown Links 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Link Checker 20 | uses: lycheeverse/lychee-action@v2 21 | with: 22 | args: --no-progress './**/*.md' 23 | fail: true 24 | -------------------------------------------------------------------------------- /.github/workflows/check-package.yml: -------------------------------------------------------------------------------- 1 | name: check-package 2 | # This workflow checks if the libraries can be built into distributable, compressed .crate files. 3 | permissions: 4 | contents: read 5 | on: 6 | push: 7 | branches: [main, v*] 8 | pull_request: 9 | branches: [main, v*] 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 13 | cancel-in-progress: true 14 | env: 15 | CARGO_TERM_COLOR: always 16 | jobs: 17 | check-package: 18 | name: Check package 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Install rust 24 | uses: actions-rust-lang/setup-rust-toolchain@v1 25 | with: 26 | rustflags: "" 27 | 28 | - name: Check openzeppelin-crypto 29 | run: cargo package -p openzeppelin-crypto --target wasm32-unknown-unknown 30 | 31 | - name: Check openzeppelin-stylus-proc 32 | run: cargo package -p openzeppelin-stylus-proc --target wasm32-unknown-unknown 33 | 34 | - name: Check openzeppelin-stylus 35 | run: cargo package -p openzeppelin-stylus --target wasm32-unknown-unknown 36 | -------------------------------------------------------------------------------- /.github/workflows/check-wasm.yml: -------------------------------------------------------------------------------- 1 | name: check-wasm 2 | # This workflow checks that the compiled wasm binary of every example contract 3 | # can be deployed to Arbitrum Stylus. 4 | permissions: 5 | contents: read 6 | on: 7 | push: 8 | branches: [main, v*] 9 | pull_request: 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | env: 14 | CARGO_TERM_COLOR: always 15 | jobs: 16 | check-wasm: 17 | name: Check WASM binary 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Install rust 23 | uses: actions-rust-lang/setup-rust-toolchain@v1 24 | with: 25 | rustflags: "" 26 | 27 | - name: Install cargo-stylus 28 | run: cargo install cargo-stylus@0.6.0 29 | 30 | - name: Run wasm check 31 | run: ./scripts/check-wasm.sh 32 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow runs our end-to-end tests suite. 2 | # 3 | # It roughly follows these steps: 4 | # - Install rust 5 | # - Install `cargo-stylus` 6 | # - Spin up `nitro-testnode` 7 | # 8 | # Contract deployments and account funding happen on a per-test basis. 9 | name: e2e 10 | permissions: 11 | contents: read 12 | on: 13 | push: 14 | branches: [main, v*] 15 | pull_request: 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 18 | cancel-in-progress: true 19 | env: 20 | CARGO_TERM_COLOR: always 21 | jobs: 22 | required: 23 | name: tests 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - name: Install rust 29 | uses: actions-rust-lang/setup-rust-toolchain@v1 30 | with: 31 | cache-key: "e2e-tests" 32 | rustflags: "" 33 | 34 | - name: Install cargo-stylus 35 | run: cargo install cargo-stylus@0.6.0 36 | 37 | - name: Setup nitro node 38 | run: ./scripts/nitro-testnode.sh -d -i 39 | 40 | - name: run integration tests 41 | run: ./scripts/e2e-tests.sh 42 | -------------------------------------------------------------------------------- /.github/workflows/gas-bench.yml: -------------------------------------------------------------------------------- 1 | name: gas-bench 2 | # This workflow checks that the compiled wasm binary of every example contract 3 | # can be deployed to Arbitrum Stylus. 4 | permissions: 5 | contents: read 6 | on: 7 | push: 8 | branches: [main, v*] 9 | pull_request: 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | env: 14 | CARGO_TERM_COLOR: always 15 | jobs: 16 | required: 17 | name: Gas usage report 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Install rust 23 | uses: actions-rust-lang/setup-rust-toolchain@v1 24 | with: 25 | cache-key: "gas-bench" 26 | rustflags: "" 27 | 28 | - name: Install cargo-stylus 29 | run: cargo install cargo-stylus@0.6.0 30 | 31 | - name: Install wasm-opt 32 | run: cargo install wasm-opt@0.116.1 33 | 34 | - name: Setup nitro node 35 | run: ./scripts/nitro-testnode.sh -d -i 36 | 37 | - name: run benches 38 | run: ./scripts/bench.sh 39 | -------------------------------------------------------------------------------- /.github/workflows/publish-openzeppelin-crypto.yml: -------------------------------------------------------------------------------- 1 | name: publish openzeppelin-crypto 2 | # This workflow publishes openzeppelin-crypto on crates.io. 3 | permissions: 4 | contents: read 5 | on: 6 | push: 7 | tags: 8 | - v* 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 11 | cancel-in-progress: true 12 | env: 13 | CARGO_TERM_COLOR: always 14 | jobs: 15 | check-publish: 16 | name: Publish openzeppelin-crypto on crates.io 17 | env: 18 | OPENZEPPELIN_CRYPTO_TOKEN: ${{ secrets.OPENZEPPELIN_CRYPTO_TOKEN }} 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Install rust 24 | uses: actions-rust-lang/setup-rust-toolchain@v1 25 | with: 26 | rustflags: "" 27 | 28 | - name: Check openzeppelin-crypto 29 | run: cargo publish -p openzeppelin-crypto --target wasm32-unknown-unknown --dry-run 30 | 31 | - name: Publish openzeppelin-crypto 32 | run: cargo publish -p openzeppelin-crypto --target wasm32-unknown-unknown --token $OPENZEPPELIN_CRYPTO_TOKEN 33 | -------------------------------------------------------------------------------- /.github/workflows/publish-openzeppelin-stylus-proc.yml: -------------------------------------------------------------------------------- 1 | name: publish openzeppelin-stylus-proc 2 | # This workflow publishes openzeppelin-stylus-proc on crates.io. 3 | permissions: 4 | contents: read 5 | on: 6 | workflow_dispatch 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 9 | cancel-in-progress: true 10 | env: 11 | CARGO_TERM_COLOR: always 12 | jobs: 13 | check-publish: 14 | name: Publish openzeppelin-stylus-proc on crates.io 15 | env: 16 | OPENZEPPELIN_STYLUS_PROC_TOKEN: ${{ secrets.OPENZEPPELIN_STYLUS_PROC_TOKEN }} 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Install rust 22 | uses: actions-rust-lang/setup-rust-toolchain@v1 23 | with: 24 | rustflags: "" 25 | 26 | - name: Check openzeppelin-stylus-proc 27 | run: cargo publish -p openzeppelin-stylus-proc --target wasm32-unknown-unknown --dry-run 28 | 29 | - name: Publish openzeppelin-stylus-proc 30 | run: cargo publish -p openzeppelin-stylus-proc --target wasm32-unknown-unknown --token $OPENZEPPELIN_STYLUS_PROC_TOKEN 31 | -------------------------------------------------------------------------------- /.github/workflows/publish-openzeppelin-stylus.yml: -------------------------------------------------------------------------------- 1 | name: publish openzeppelin-stylus 2 | # This workflow publishes openzeppelin-stylus on crates.io. 3 | permissions: 4 | contents: read 5 | on: 6 | push: 7 | tags: 8 | - v* 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 11 | cancel-in-progress: true 12 | env: 13 | CARGO_TERM_COLOR: always 14 | jobs: 15 | check-publish: 16 | name: Publish openzeppelin-stylus on crates.io 17 | env: 18 | OPENZEPPELIN_STYLUS_TOKEN: ${{ secrets.OPENZEPPELIN_STYLUS_TOKEN }} 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Install rust 24 | uses: actions-rust-lang/setup-rust-toolchain@v1 25 | with: 26 | rustflags: "" 27 | 28 | - name: Check openzeppelin-stylus 29 | run: cargo publish -p openzeppelin-stylus --target wasm32-unknown-unknown --dry-run 30 | 31 | - name: Publish openzeppelin-stylus 32 | run: cargo publish -p openzeppelin-stylus --target wasm32-unknown-unknown --token $OPENZEPPELIN_STYLUS_TOKEN 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | 3 | .env 4 | 5 | **/node_modules/ 6 | 7 | docs/build/ 8 | 9 | **/.DS_Store 10 | 11 | **/nitro-testnode 12 | 13 | lcov.info 14 | 15 | lcov.infos 16 | -------------------------------------------------------------------------------- /.linkspector.yml: -------------------------------------------------------------------------------- 1 | dirs: 2 | - docs/modules/ROOT/pages/ 3 | 4 | # There's a bug in linkspector that causes it to only allow one file extension type 5 | # when using it with directories. 6 | fileExtensions: 7 | - adoc 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | There are many ways to contribute to OpenZeppelin Contracts for Stylus. 4 | 5 | ## Troubleshooting 6 | 7 | You can help other users in the community to solve their smart contract issues 8 | in the [OpenZeppelin Forum]. 9 | 10 | [OpenZeppelin Forum]: https://forum.openzeppelin.com/ 11 | 12 | ## Opening an issue 13 | 14 | You can [open an issue] to suggest a feature or report a minor bug. 15 | 16 | If you believe your issue may be due to user error and not a problem in the 17 | library, consider instead posting a question on the [OpenZeppelin Forum]. 18 | 19 | Before opening an issue, be sure to search through the existing open and closed 20 | issues, and consider posting a comment in one of those instead. 21 | 22 | When requesting a new feature, include as many details as you can, especially 23 | around the use cases that motivate it. Features are prioritized according to 24 | the impact they may have on the ecosystem, so we appreciate information showing 25 | that the impact could be high. 26 | 27 | [open an issue]: https://github.com/OpenZeppelin/rust-contracts-stylus/issues/new/choose 28 | 29 | ## Finding a proper issue 30 | 31 | ### New Contributors 32 | 33 | If you're new and looking for a good place to start, check out issues labeled ["good first issue"]. 34 | 35 | ["good first issue"]: https://github.com/OpenZeppelin/rust-contracts-stylus/issues?q=is%3Aopen+label%3A%22good+first+issue%22+no%3Aassignee 36 | 37 | ### More "experienced" Contributors 38 | 39 | If you are already more familiar with our codebase and Stylus here are some recommended views to to find issues you can work on. We just ask you to comment on the issue, that way one of the maintainers can assign it to you. 40 | 41 | - [A Low-Priority GitHub Issue from the current cycle](https://github.com/orgs/OpenZeppelin/projects/35/views/8?filterQuery=milestone%3A%22Release+v0.2.0%22+cycle%3A%40current+priority%3A%22priority%3A+low%22+no%3Aassignee). 42 | - [A Medium-Priority GitHub Issue from the next cycle](https://github.com/orgs/OpenZeppelin/projects/35/views/8?filterQuery=milestone%3A%22Release+v0.2.0%22+cycle%3A%40next+priority%3A%22priority%3A+medium%22+no%3Aassignee). 43 | - [A Low-Priority GitHub Issue from the next cycle](https://github.com/orgs/OpenZeppelin/projects/35/views/8?filterQuery=milestone%3A%22Release+v0.2.0%22+cycle%3A%40next+priority%3A%22priority%3A+low%22+no%3Aassignee). 44 | - [An Unplanned GitHub Issue](https://github.com/orgs/OpenZeppelin/projects/35/views/8?filterQuery=no%3Acycle+status%3ATodo+no%3Aassignee). 45 | 46 | ## Submitting a pull request 47 | 48 | If you would like to contribute code or documentation you may do so by forking 49 | the repository and submitting a pull request. 50 | 51 | Any non-trivial code contribution must be first discussed with the maintainers 52 | in an issue (see [Opening an issue](#opening-an-issue)). Only very minor 53 | changes are accepted without prior discussion. 54 | 55 | Make sure to read and follow the [engineering guidelines](./GUIDELINES.md). Run 56 | linter and tests to make sure your pull request is good before submitting it. 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2025 Zeppelin Group Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | Past audits can be found in [`audits/`](./audits). 4 | 5 | Please report any security issues you find to security@openzeppelin.com. 6 | -------------------------------------------------------------------------------- /audits/2024-10-v0.1.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenZeppelin/rust-contracts-stylus/14e3127f69fbe138313d1a1fc8df7b725c69dc76/audits/2024-10-v0.1.0.pdf -------------------------------------------------------------------------------- /benches/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benches" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | openzeppelin-crypto.workspace = true 12 | alloy-primitives = { workspace = true, features = ["tiny-keccak"] } 13 | alloy.workspace = true 14 | stylus-sdk.workspace = true 15 | tokio.workspace = true 16 | futures.workspace = true 17 | eyre.workspace = true 18 | e2e.workspace = true 19 | serde = "1.0.203" 20 | keccak-const = "0.2.0" 21 | itertools = "0.13.0" 22 | -------------------------------------------------------------------------------- /benches/src/erc1155_metadata_uri.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | network::{AnyNetwork, EthereumWallet}, 3 | primitives::Address, 4 | providers::ProviderBuilder, 5 | sol, 6 | sol_types::SolCall, 7 | uint, 8 | }; 9 | use e2e::{constructor, receipt, Account}; 10 | 11 | use crate::{ 12 | report::{ContractReport, FunctionReport}, 13 | Opt, 14 | }; 15 | 16 | sol!( 17 | #[sol(rpc)] 18 | contract Erc1155MetadataUri { 19 | function uri(uint256 id) external view returns (string memory uri); 20 | function setTokenURI(uint256 tokenId, string memory tokenURI) external; 21 | function setBaseURI(string memory tokenURI) external; 22 | } 23 | ); 24 | 25 | const URI: &str = "https://github.com/OpenZeppelin/rust-contracts-stylus"; 26 | const BASE_URI: &str = "https://github.com"; 27 | const TOKEN_URI: &str = "/some/token/uri"; 28 | 29 | pub async fn bench() -> eyre::Result { 30 | ContractReport::generate("Erc1155MetadataUri", run).await 31 | } 32 | 33 | pub async fn run(cache_opt: Opt) -> eyre::Result> { 34 | let alice = Account::new().await?; 35 | let alice_wallet = ProviderBuilder::new() 36 | .network::() 37 | .with_recommended_fillers() 38 | .wallet(EthereumWallet::from(alice.signer.clone())) 39 | .on_http(alice.url().parse()?); 40 | 41 | let contract_addr = deploy(&alice, cache_opt).await?; 42 | 43 | let contract = Erc1155MetadataUri::new(contract_addr, &alice_wallet); 44 | 45 | let token_id = uint!(1_U256); 46 | 47 | // IMPORTANT: Order matters! 48 | use Erc1155MetadataUri::*; 49 | #[rustfmt::skip] 50 | let receipts = vec![ 51 | (setTokenURICall::SIGNATURE, receipt!(contract.setTokenURI(token_id, TOKEN_URI.to_owned()))?), 52 | (setBaseURICall::SIGNATURE, receipt!(contract.setBaseURI(BASE_URI.to_owned()))?), 53 | (uriCall::SIGNATURE, receipt!(contract.uri(token_id))?), 54 | ]; 55 | 56 | receipts 57 | .into_iter() 58 | .map(FunctionReport::new) 59 | .collect::>>() 60 | } 61 | 62 | async fn deploy(account: &Account, cache_opt: Opt) -> eyre::Result
{ 63 | crate::deploy( 64 | account, 65 | "erc1155-metadata-uri", 66 | Some(constructor!(URI.to_string())), 67 | cache_opt, 68 | ) 69 | .await 70 | } 71 | -------------------------------------------------------------------------------- /benches/src/erc1155_supply.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | network::{AnyNetwork, EthereumWallet}, 3 | primitives::Address, 4 | providers::ProviderBuilder, 5 | sol, 6 | sol_types::SolCall, 7 | uint, 8 | }; 9 | use e2e::{receipt, Account}; 10 | 11 | use crate::{ 12 | report::{ContractReport, FunctionReport}, 13 | Opt, 14 | }; 15 | 16 | sol!( 17 | #[sol(rpc)] 18 | contract Erc1155Supply { 19 | function mint(address to, uint256 id, uint256 amount, bytes memory data) external; 20 | function totalSupply(uint256 id) external view returns (uint256); 21 | function totalSupply() external view returns (uint256); 22 | function exists(uint256 id) external view returns (bool); 23 | } 24 | ); 25 | 26 | pub async fn bench() -> eyre::Result { 27 | ContractReport::generate("Erc1155Supply", run).await 28 | } 29 | 30 | pub async fn run(cache_opt: Opt) -> eyre::Result> { 31 | let alice = Account::new().await?; 32 | let alice_addr = alice.address(); 33 | let alice_wallet = ProviderBuilder::new() 34 | .network::() 35 | .with_recommended_fillers() 36 | .wallet(EthereumWallet::from(alice.signer.clone())) 37 | .on_http(alice.url().parse()?); 38 | 39 | let contract_addr = deploy(&alice, cache_opt).await?; 40 | 41 | let contract = Erc1155Supply::new(contract_addr, &alice_wallet); 42 | 43 | let token = uint!(1_U256); 44 | let value = uint!(100_U256); 45 | 46 | // IMPORTANT: Order matters! 47 | use Erc1155Supply::*; 48 | #[rustfmt::skip] 49 | let receipts = vec![ 50 | (mintCall::SIGNATURE, receipt!(contract.mint(alice_addr, token, value, vec![].into()))?), 51 | (existsCall::SIGNATURE, receipt!(contract.exists(token))?), 52 | (totalSupply_0Call::SIGNATURE, receipt!(contract.totalSupply_0(token))?), 53 | (totalSupply_1Call::SIGNATURE, receipt!(contract.totalSupply_1())?), 54 | ]; 55 | 56 | receipts 57 | .into_iter() 58 | .map(FunctionReport::new) 59 | .collect::>>() 60 | } 61 | 62 | async fn deploy(account: &Account, cache_opt: Opt) -> eyre::Result
{ 63 | crate::deploy(account, "erc1155-supply", None, cache_opt).await 64 | } 65 | -------------------------------------------------------------------------------- /benches/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use alloy::primitives::Address; 4 | use alloy_primitives::U128; 5 | use e2e::{Account, Constructor}; 6 | use eyre::WrapErr; 7 | use serde::Deserialize; 8 | 9 | pub mod access_control; 10 | pub mod erc1155; 11 | pub mod erc1155_metadata_uri; 12 | pub mod erc1155_supply; 13 | pub mod erc20; 14 | pub mod erc721; 15 | pub mod merkle_proofs; 16 | pub mod ownable; 17 | pub mod pedersen; 18 | pub mod poseidon; 19 | pub mod poseidon_asm_sol; 20 | pub mod poseidon_sol; 21 | pub mod report; 22 | pub mod vesting_wallet; 23 | 24 | #[derive(Debug, Deserialize)] 25 | struct ArbOtherFields { 26 | #[serde(rename = "gasUsedForL1")] 27 | gas_used_for_l1: U128, 28 | #[allow(dead_code)] 29 | #[serde(rename = "l1BlockNumber")] 30 | l1_block_number: String, 31 | } 32 | 33 | /// Optimisation options for the contract. 34 | /// 35 | /// Cache or cache optimized WASM. 36 | #[derive(Clone)] 37 | pub enum Opt { 38 | None, 39 | Cache, 40 | CacheWasmOpt, 41 | } 42 | 43 | async fn deploy( 44 | account: &Account, 45 | contract_name: &str, 46 | constructor: Option, 47 | opt: Opt, 48 | ) -> eyre::Result
{ 49 | let manifest_dir = 50 | std::env::current_dir().context("should get current dir from env")?; 51 | 52 | let contract_type = match opt { 53 | Opt::CacheWasmOpt => "example_opt", 54 | Opt::None | Opt::Cache => "example", 55 | }; 56 | 57 | let wasm_path = manifest_dir 58 | .join("target") 59 | .join("wasm32-unknown-unknown") 60 | .join("release") 61 | .join(format!( 62 | "{}_{}.wasm", 63 | contract_name.replace('-', "_"), 64 | contract_type 65 | )); 66 | 67 | let deployer = match constructor { 68 | Some(constructor) => { 69 | account.as_deployer().with_constructor(constructor) 70 | } 71 | None => account.as_deployer(), 72 | }; 73 | 74 | let address = deployer.deploy_wasm(&wasm_path).await?.contract_address; 75 | 76 | match opt { 77 | Opt::Cache | Opt::CacheWasmOpt => { 78 | cache_contract(account, address, 0)?; 79 | } 80 | Opt::None => {} 81 | } 82 | 83 | Ok(address) 84 | } 85 | 86 | /// Try to cache a contract on the stylus network. 87 | /// Already cached contracts won't be cached, and this function will not return 88 | /// an error. 89 | /// Output will be forwarded to the child process. 90 | fn cache_contract( 91 | account: &Account, 92 | contract_addr: Address, 93 | bid: u32, 94 | ) -> eyre::Result<()> { 95 | // We don't need a status code. 96 | // Since it is not zero when the contract is already cached. 97 | Command::new("cargo") 98 | .args(["stylus", "cache", "bid"]) 99 | .args(["-e", &env("RPC_URL")?]) 100 | .args(["--private-key", &format!("0x{}", account.pk())]) 101 | .arg(contract_addr.to_string()) 102 | .arg(bid.to_string()) 103 | .status() 104 | .context("failed to execute `cargo stylus cache bid` command")?; 105 | Ok(()) 106 | } 107 | 108 | /// Load the `name` environment variable. 109 | fn env(name: &str) -> eyre::Result { 110 | std::env::var(name).wrap_err(format!("failed to load {name}")) 111 | } 112 | -------------------------------------------------------------------------------- /benches/src/main.rs: -------------------------------------------------------------------------------- 1 | use benches::{ 2 | access_control, erc1155, erc1155_metadata_uri, erc20, erc721, 3 | merkle_proofs, ownable, pedersen, poseidon, poseidon_asm_sol, poseidon_sol, 4 | report::BenchmarkReport, 5 | }; 6 | use futures::FutureExt; 7 | use itertools::Itertools; 8 | 9 | #[tokio::main] 10 | async fn main() -> eyre::Result<()> { 11 | let benchmarks = [ 12 | access_control::bench().boxed(), 13 | erc20::bench().boxed(), 14 | erc721::bench().boxed(), 15 | merkle_proofs::bench().boxed(), 16 | ownable::bench().boxed(), 17 | erc1155::bench().boxed(), 18 | erc1155_metadata_uri::bench().boxed(), 19 | pedersen::bench().boxed(), 20 | poseidon_sol::bench().boxed(), 21 | poseidon_asm_sol::bench().boxed(), 22 | poseidon::bench().boxed(), 23 | ]; 24 | 25 | // Run benchmarks max 3 at the same time. 26 | // Otherwise, nitro test node can overload and revert transaction. 27 | const MAX_PARALLEL: usize = 3; 28 | let mut report = BenchmarkReport::default(); 29 | for chunk in &benchmarks.into_iter().chunks(MAX_PARALLEL) { 30 | report = futures::future::try_join_all(chunk) 31 | .await? 32 | .into_iter() 33 | .fold(report, BenchmarkReport::merge_with); 34 | } 35 | 36 | println!(); 37 | println!("{report}"); 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /benches/src/ownable.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | network::{AnyNetwork, EthereumWallet}, 3 | primitives::Address, 4 | providers::ProviderBuilder, 5 | sol, 6 | sol_types::SolCall, 7 | }; 8 | use e2e::{constructor, receipt, Account}; 9 | 10 | use crate::{ 11 | report::{ContractReport, FunctionReport}, 12 | Opt, 13 | }; 14 | 15 | sol!( 16 | #[sol(rpc)] 17 | contract Ownable { 18 | function owner() external view returns (address owner); 19 | function renounceOwnership() external onlyOwner; 20 | function transferOwnership(address newOwner) external; 21 | } 22 | ); 23 | 24 | pub async fn bench() -> eyre::Result { 25 | ContractReport::generate("Ownable", run).await 26 | } 27 | 28 | pub async fn run(cache_opt: Opt) -> eyre::Result> { 29 | let alice = Account::new().await?; 30 | let alice_wallet = ProviderBuilder::new() 31 | .network::() 32 | .with_recommended_fillers() 33 | .wallet(EthereumWallet::from(alice.signer.clone())) 34 | .on_http(alice.url().parse()?); 35 | 36 | let bob = Account::new().await?; 37 | let bob_addr = bob.address(); 38 | let bob_wallet = ProviderBuilder::new() 39 | .network::() 40 | .with_recommended_fillers() 41 | .wallet(EthereumWallet::from(bob.signer.clone())) 42 | .on_http(bob.url().parse()?); 43 | 44 | let contract_addr = deploy(&alice, cache_opt).await?; 45 | 46 | let contract = Ownable::new(contract_addr, &alice_wallet); 47 | let contract_bob = Ownable::new(contract_addr, &bob_wallet); 48 | 49 | // IMPORTANT: Order matters! 50 | use Ownable::*; 51 | #[rustfmt::skip] 52 | let receipts = vec![ 53 | (ownerCall::SIGNATURE, receipt!(contract.owner())?), 54 | (transferOwnershipCall::SIGNATURE, receipt!(contract.transferOwnership(bob_addr))?), 55 | (renounceOwnershipCall::SIGNATURE, receipt!(contract_bob.renounceOwnership())?), 56 | ]; 57 | 58 | receipts 59 | .into_iter() 60 | .map(FunctionReport::new) 61 | .collect::>>() 62 | } 63 | 64 | async fn deploy(account: &Account, cache_opt: Opt) -> eyre::Result
{ 65 | crate::deploy( 66 | account, 67 | "ownable", 68 | Some(constructor!(account.address())), 69 | cache_opt, 70 | ) 71 | .await 72 | } 73 | -------------------------------------------------------------------------------- /benches/src/pedersen.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | network::{AnyNetwork, EthereumWallet}, 3 | primitives::Address, 4 | providers::ProviderBuilder, 5 | sol, 6 | sol_types::SolCall, 7 | }; 8 | use e2e::{receipt, Account}; 9 | use openzeppelin_crypto::arithmetic::{ 10 | uint::{from_str_hex, U256}, 11 | BigInteger, 12 | }; 13 | 14 | use crate::{ 15 | report::{ContractReport, FunctionReport}, 16 | Opt, 17 | }; 18 | 19 | sol!( 20 | #[sol(rpc)] 21 | contract PedersenExample { 22 | #[derive(Debug)] 23 | function hash(uint256[2] memory inputs) external view returns (uint256 hash); 24 | } 25 | ); 26 | 27 | pub async fn bench() -> eyre::Result { 28 | ContractReport::generate("Pedersen", run).await 29 | } 30 | 31 | fn to_alloy_u256(value: &U256) -> alloy_primitives::U256 { 32 | alloy_primitives::U256::from_le_slice(&value.into_bytes_le()) 33 | } 34 | pub async fn run(cache_opt: Opt) -> eyre::Result> { 35 | let alice = Account::new().await?; 36 | let alice_wallet = ProviderBuilder::new() 37 | .network::() 38 | .with_recommended_fillers() 39 | .wallet(EthereumWallet::from(alice.signer.clone())) 40 | .on_http(alice.url().parse()?); 41 | 42 | let contract_addr = deploy(&alice, cache_opt).await?; 43 | 44 | let contract = PedersenExample::new(contract_addr, &alice_wallet); 45 | 46 | let input_1 = to_alloy_u256(&from_str_hex( 47 | "3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb", 48 | )); 49 | let input_2 = to_alloy_u256(&from_str_hex( 50 | "208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a", 51 | )); 52 | 53 | #[rustfmt::skip] 54 | let receipts = vec![ 55 | (PedersenExample::hashCall::SIGNATURE, receipt!(contract.hash([input_1, input_2]))?), 56 | ]; 57 | 58 | receipts 59 | .into_iter() 60 | .map(FunctionReport::new) 61 | .collect::>>() 62 | } 63 | 64 | async fn deploy(account: &Account, cache_opt: Opt) -> eyre::Result
{ 65 | crate::deploy(account, "pedersen", None, cache_opt).await 66 | } 67 | -------------------------------------------------------------------------------- /benches/src/poseidon.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | network::{AnyNetwork, EthereumWallet}, 3 | primitives::Address, 4 | providers::ProviderBuilder, 5 | sol, 6 | sol_types::SolCall, 7 | }; 8 | use alloy_primitives::uint; 9 | use e2e::{receipt, Account}; 10 | 11 | use crate::{ 12 | report::{ContractReport, FunctionReport}, 13 | Opt, 14 | }; 15 | 16 | sol!( 17 | #[sol(rpc)] 18 | contract PoseidonExample { 19 | #[derive(Debug)] 20 | function hash(uint256[2] memory inputs) external view returns (uint256 hash); 21 | } 22 | ); 23 | 24 | pub async fn bench() -> eyre::Result { 25 | ContractReport::generate("Poseidon", run).await 26 | } 27 | 28 | pub async fn run(cache_opt: Opt) -> eyre::Result> { 29 | let alice = Account::new().await?; 30 | let alice_wallet = ProviderBuilder::new() 31 | .network::() 32 | .with_recommended_fillers() 33 | .wallet(EthereumWallet::from(alice.signer.clone())) 34 | .on_http(alice.url().parse()?); 35 | 36 | let contract_addr = deploy(&alice, cache_opt).await?; 37 | 38 | let contract = PoseidonExample::new(contract_addr, &alice_wallet); 39 | 40 | #[rustfmt::skip] 41 | let receipts = vec![ 42 | (PoseidonExample::hashCall::SIGNATURE, receipt!(contract.hash([uint!(123_U256), uint!(123456_U256)]))?), 43 | ]; 44 | 45 | receipts 46 | .into_iter() 47 | .map(FunctionReport::new) 48 | .collect::>>() 49 | } 50 | 51 | async fn deploy(account: &Account, cache_opt: Opt) -> eyre::Result
{ 52 | crate::deploy(account, "poseidon", None, cache_opt).await 53 | } 54 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | doc-valid-idents = ["OpenZeppelin", ".."] 2 | -------------------------------------------------------------------------------- /contracts-proc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openzeppelin-stylus-proc" 3 | description = "Procedural macros for OpenZeppelin Stylus contracts" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | keywords = ["arbitrum", "ethereum", "stylus", "smart-contracts", "standards"] 9 | categories = ["cryptography::cryptocurrencies", "no-std", "wasm"] 10 | version = "0.2.1" 11 | 12 | [dependencies] 13 | proc-macro2.workspace = true 14 | quote.workspace = true 15 | syn.workspace = true 16 | convert_case = "0.6.0" 17 | 18 | [lints] 19 | workspace = true 20 | 21 | [lib] 22 | proc-macro = true 23 | -------------------------------------------------------------------------------- /contracts-proc/README.md: -------------------------------------------------------------------------------- 1 | # OpenZeppelin Stylus Procedural Macros 2 | 3 | Procedural macros for [OpenZeppelin Stylus Contracts](../contracts), providing tools to streamline trait definitions, interface ID computation, and Solidity compatibility in Rust-based Stylus Contracts. 4 | 5 | ## Overview 6 | 7 | This crate offers procedural macros for OpenZeppelin Stylus Contracts, specifically targeting the computation of Solidity `interfaceId` value and enhancing trait implementations with clear and robust syntax. 8 | 9 | ### Key Features 10 | 11 | - **`#[interface_id]` Macro:** Adds `interface_id()` function that computes Solidity-compatible interface ID for traits. 12 | - **`#[selector]` Attribute:** Overrides function names to align with Solidity method signatures. 13 | 14 | ## Usage 15 | 16 | ### `#[interface_id]` 17 | 18 | Annotate a Rust trait with `#[interface_id]` to add the Solidity-compatible interface ID calculation: 19 | 20 | ```rust,ignore 21 | use openzeppelin_stylus_proc::interface_id; 22 | 23 | #[interface_id] 24 | pub trait IErc721 { 25 | type Error: Into>; 26 | 27 | fn balance_of(&self, owner: Address) -> Result; 28 | fn owner_of(&self, token_id: U256) -> Result; 29 | 30 | // ... 31 | } 32 | ``` 33 | 34 | This will add `interface_id()` function that caluclates interface ID based on the XOR of the function selectors. 35 | 36 | ### `#[selector]` 37 | 38 | Override the Solidity function selector explicitly: 39 | 40 | ```rust,ignore 41 | fn safe_transfer_from( 42 | &mut self, 43 | from: Address, 44 | to: Address, 45 | token_id: U256, 46 | ) -> Result<(), Self::Error>; 47 | 48 | // Solidity allows function overloading, but Rust does not, so we map 49 | // the Rust function name to the appropriate Solidity function name. 50 | #[selector(name = "safeTransferFrom")] 51 | fn safe_transfer_from_with_data( 52 | &mut self, 53 | from: Address, 54 | to: Address, 55 | token_id: U256, 56 | data: Bytes, 57 | ) -> Result<(), Self::Error>; 58 | ``` 59 | 60 | This ensures compatibility with Solidity's naming conventions. 61 | 62 | ## Security 63 | 64 | Refer to our [Security Policy](../SECURITY.md) for more details. 65 | -------------------------------------------------------------------------------- /contracts-proc/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | extern crate proc_macro; 4 | use proc_macro::TokenStream; 5 | 6 | /// Shorthand to print nice errors. 7 | /// 8 | /// Note that it's defined before the module declarations. 9 | macro_rules! error { 10 | ($tokens:expr, $($msg:expr),+ $(,)?) => {{ 11 | let error = syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+)); 12 | return error.to_compile_error().into(); 13 | }}; 14 | (@ $tokens:expr, $($msg:expr),+ $(,)?) => {{ 15 | return Err(syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+))) 16 | }}; 17 | } 18 | 19 | mod interface_id; 20 | 21 | /// Computes the interface id as an associated constant `INTERFACE_ID` for the 22 | /// trait that describes contract's abi. 23 | /// 24 | /// Selector collision should be handled with 25 | /// macro `#[selector(name = "actualSolidityMethodName")]` on top of the method. 26 | /// 27 | /// # Examples 28 | /// 29 | /// ```rust,ignore 30 | /// #[interface_id] 31 | /// pub trait IErc721 { 32 | /// fn balance_of(&self, owner: Address) -> Result>; 33 | /// 34 | /// fn owner_of(&self, token_id: U256) -> Result>; 35 | /// 36 | /// fn safe_transfer_from( 37 | /// &mut self, 38 | /// from: Address, 39 | /// to: Address, 40 | /// token_id: U256, 41 | /// ) -> Result<(), Vec>; 42 | /// 43 | /// #[selector(name = "safeTransferFrom")] 44 | /// fn safe_transfer_from_with_data( 45 | /// &mut self, 46 | /// from: Address, 47 | /// to: Address, 48 | /// token_id: U256, 49 | /// data: Bytes, 50 | /// ) -> Result<(), Vec>; 51 | /// } 52 | /// 53 | /// impl IErc165 for Erc721 { 54 | /// fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { 55 | /// ::interface_id() == interface_id 56 | /// || ::interface_id() == interface_id 57 | /// } 58 | /// } 59 | /// ``` 60 | #[proc_macro_attribute] 61 | pub fn interface_id(attr: TokenStream, input: TokenStream) -> TokenStream { 62 | interface_id::interface_id(&attr, input) 63 | } 64 | -------------------------------------------------------------------------------- /contracts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openzeppelin-stylus" 3 | description = "OpenZeppelin Contracts for Stylus" 4 | edition.workspace = true 5 | categories = ["cryptography::cryptocurrencies", "no-std", "wasm"] 6 | keywords = ["arbitrum", "ethereum", "stylus", "smart-contracts", "standards"] 7 | license.workspace = true 8 | repository.workspace = true 9 | version.workspace = true 10 | 11 | [dependencies] 12 | alloy-primitives.workspace = true 13 | alloy-sol-types.workspace = true 14 | alloy-sol-macro.workspace = true 15 | alloy-sol-macro-expander.workspace = true 16 | alloy-sol-macro-input.workspace = true 17 | stylus-sdk.workspace = true 18 | keccak-const.workspace = true 19 | openzeppelin-stylus-proc.workspace = true 20 | 21 | [dev-dependencies] 22 | alloy-primitives = { workspace = true, features = ["arbitrary"] } 23 | motsu.workspace = true 24 | 25 | [features] 26 | reentrant = ["stylus-sdk/reentrant"] 27 | export-abi = ["stylus-sdk/export-abi"] 28 | 29 | [lib] 30 | crate-type = ["lib"] 31 | 32 | [lints] 33 | workspace = true 34 | -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # OpenZeppelin Contracts for Arbitrum Stylus 2 | 3 | Robust, reliable, and secure smart contracts for the Arbitrum Stylus upgrade. 4 | 5 | > [!WARNING] 6 | > Note that `contracts` is still `0.*.*`, so breaking changes 7 | > [may occur at any time](https://semver.org/#spec-item-4). If you must depend 8 | > on `contracts`, we recommend pinning to a specific version, i.e., `=0.y.z`. 9 | 10 | ## Security 11 | 12 | Refer to our [Security Policy](../SECURITY.md) for more details. 13 | -------------------------------------------------------------------------------- /contracts/proptest-regressions/utils/math/alloy.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc f58ea2d53136bc78804abf3cc86633f05fad78c146b9462904fbac613cd74a2b # shrinks to x = 0, y = 0 8 | cc eef31162ab6714625e038aa5625425e31970b3637872924e6d6ab6de8f9cc12a # shrinks to x = 6277101735386680763835789423207666416102355444464034512896, y = 6277101735386680763835789423207666416102355444464034512896 9 | -------------------------------------------------------------------------------- /contracts/src/access/control/extensions/mod.rs: -------------------------------------------------------------------------------- 1 | //! Optional extensions of [`super::AccessControl`] contract. 2 | pub mod enumerable; 3 | 4 | pub use enumerable::{AccessControlEnumerable, IAccessControlEnumerable}; 5 | -------------------------------------------------------------------------------- /contracts/src/access/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contracts providing ways to restrict who can access the functions of another 2 | //! contracts or when they can do it. 3 | pub mod control; 4 | pub mod ownable; 5 | pub mod ownable_two_step; 6 | -------------------------------------------------------------------------------- /contracts/src/finance/mod.rs: -------------------------------------------------------------------------------- 1 | //! Primitives for financial systems. 2 | pub mod vesting_wallet; 3 | -------------------------------------------------------------------------------- /contracts/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # OpenZeppelin Contracts for Stylus 3 | 4 | A library for secure smart contract development written in Rust for 5 | [Arbitrum Stylus](https://docs.arbitrum.io/stylus/gentle-introduction). 6 | This library offers common smart contract primitives and affordances that take 7 | advantage of the nature of Stylus. 8 | 9 | ## Usage 10 | 11 | To start using it, add `openzeppelin-stylus` to your `Cargo.toml`, or simply run 12 | `cargo add openzeppelin-stylus`. 13 | 14 | ```toml 15 | [dependencies] 16 | openzeppelin-stylus = "x.x.x" 17 | ``` 18 | 19 | We recommend pinning to a specific version -- expect rapid iteration. 20 | 21 | Once defined as a dependency, use one of our pre-defined implementations by 22 | importing them: 23 | 24 | ```ignore 25 | use openzeppelin_stylus::token::erc20::{self, Erc20, IErc20}; 26 | use stylus_sdk::{ 27 | alloy_primitives::{Address, U256}, 28 | prelude::*, 29 | }; 30 | 31 | #[entrypoint] 32 | #[storage] 33 | struct MyContract { 34 | pub erc20: Erc20, 35 | } 36 | 37 | #[public] 38 | #[implements(IErc20)] 39 | impl MyContract {} 40 | 41 | #[public] 42 | impl IErc20 for MyContract { 43 | type Error = erc20::Error; 44 | 45 | fn total_supply(&self) -> U256 { 46 | self.erc20.total_supply() 47 | } 48 | 49 | fn balance_of(&self, account: Address) -> U256 { 50 | self.erc20.balance_of(account) 51 | } 52 | 53 | fn transfer( 54 | &mut self, 55 | to: Address, 56 | value: U256, 57 | ) -> Result { 58 | self.erc20.transfer(to, value) 59 | } 60 | 61 | fn allowance(&self, owner: Address, spender: Address) -> U256 { 62 | self.erc20.allowance(owner, spender) 63 | } 64 | 65 | fn approve( 66 | &mut self, 67 | spender: Address, 68 | value: U256, 69 | ) -> Result { 70 | self.erc20.approve(spender, value) 71 | } 72 | 73 | fn transfer_from( 74 | &mut self, 75 | from: Address, 76 | to: Address, 77 | value: U256, 78 | ) -> Result { 79 | self.erc20.transfer_from(from, to, value) 80 | } 81 | } 82 | ``` 83 | */ 84 | 85 | #![allow( 86 | clippy::module_name_repetitions, 87 | clippy::used_underscore_items, 88 | deprecated 89 | )] 90 | #![cfg_attr(not(any(test, feature = "export-abi")), no_std, no_main)] 91 | #![cfg_attr(coverage_nightly, feature(coverage_attribute))] 92 | #![deny(rustdoc::broken_intra_doc_links)] 93 | extern crate alloc; 94 | 95 | pub mod access; 96 | pub mod finance; 97 | pub mod token; 98 | pub mod utils; 99 | -------------------------------------------------------------------------------- /contracts/src/token/common/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contracts that are common to multiple token standards. 2 | pub mod erc2981; 3 | -------------------------------------------------------------------------------- /contracts/src/token/erc1155/extensions/mod.rs: -------------------------------------------------------------------------------- 1 | //! Common extensions to the ERC-1155 standard. 2 | pub mod burnable; 3 | pub mod metadata_uri; 4 | pub mod supply; 5 | pub mod uri_storage; 6 | 7 | pub use burnable::IErc1155Burnable; 8 | pub use metadata_uri::{Erc1155MetadataUri, IErc1155MetadataUri}; 9 | pub use supply::{Erc1155Supply, IErc1155Supply}; 10 | pub use uri_storage::{Erc1155UriStorage, IErc1155UriStorage}; 11 | -------------------------------------------------------------------------------- /contracts/src/token/erc1155/receiver.rs: -------------------------------------------------------------------------------- 1 | //! Module with an interface required for smart contract in order to receive 2 | //! ERC-1155 token transfers. 3 | #![allow(missing_docs)] 4 | #![cfg_attr(coverage_nightly, coverage(off))] 5 | use alloc::vec; 6 | 7 | use stylus_sdk::prelude::sol_interface; 8 | 9 | sol_interface! { 10 | /// [`super::Erc1155`] token receiver interface. 11 | /// 12 | /// Interface for any contract that wants to support safe transfers from 13 | /// [`super::Erc1155`] asset contracts. 14 | interface IERC1155Receiver { 15 | /// Handles the receipt of a single ERC-1155 token type. This function 16 | /// is called at the end of [`super::IErc1155::safe_transfer_from`] 17 | /// after the balance has been updated. 18 | /// 19 | /// NOTE: To accept the transfer, this must return 20 | /// [`super::SINGLE_TRANSFER_FN_SELECTOR`], or its own function 21 | /// selector. 22 | /// 23 | /// # Arguments 24 | /// 25 | /// # Arguments 26 | /// 27 | /// * `operator` - The address which initiated the transfer. 28 | /// * `from` - The address which previously owned the token. 29 | /// * `id` - The ID of the token being transferred. 30 | /// * `value` - The amount of tokens being transferred. 31 | /// * `data` - Additional data with no specified format. 32 | #[allow(missing_docs)] 33 | function onERC1155Received( 34 | address operator, 35 | address from, 36 | uint256 id, 37 | uint256 value, 38 | bytes calldata data 39 | ) external returns (bytes4); 40 | 41 | /// Handles the receipt of multiple ERC-1155 token types. This function 42 | /// is called at the end of 43 | /// [`super::IErc1155::safe_batch_transfer_from`] after the balances 44 | /// have been updated. 45 | /// 46 | /// NOTE: To accept the transfer(s), this must return 47 | /// [`super::BATCH_TRANSFER_FN_SELECTOR`], or its own function selector. 48 | /// 49 | /// # Arguments 50 | /// 51 | /// # Arguments 52 | /// 53 | /// * `operator` - The address which initiated the batch transfer. 54 | /// * `from` - The address which previously owned the token. 55 | /// * `ids` - An array containing ids of each token being transferred 56 | /// (order and length must match `values` array). 57 | /// * `values` - An array containing amounts of each token being 58 | /// transferred (order and length must match `ids` array). 59 | /// * `data` - Additional data with no specified format. 60 | #[allow(missing_docs)] 61 | function onERC1155BatchReceived( 62 | address operator, 63 | address from, 64 | uint256[] calldata ids, 65 | uint256[] calldata values, 66 | bytes calldata data 67 | ) external returns (bytes4); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contracts/src/token/erc20/extensions/mod.rs: -------------------------------------------------------------------------------- 1 | //! Common extensions to the ERC-20 standard. 2 | pub mod burnable; 3 | pub mod capped; 4 | pub mod erc4626; 5 | pub mod flash_mint; 6 | pub mod metadata; 7 | pub mod permit; 8 | pub mod wrapper; 9 | 10 | pub use burnable::IErc20Burnable; 11 | pub use capped::{Capped, ICapped}; 12 | pub use erc4626::{Erc4626, IErc4626}; 13 | pub use flash_mint::{Erc20FlashMint, IErc3156FlashLender}; 14 | pub use metadata::{Erc20Metadata, IErc20Metadata}; 15 | pub use permit::{Erc20Permit, IErc20Permit}; 16 | pub use wrapper::{Erc20Wrapper, IErc20Wrapper}; 17 | -------------------------------------------------------------------------------- /contracts/src/token/erc20/interface.rs: -------------------------------------------------------------------------------- 1 | //! Solidity Interface of the ERC-20 token. 2 | pub use token::*; 3 | 4 | mod token { 5 | #![allow(missing_docs)] 6 | #![cfg_attr(coverage_nightly, coverage(off))] 7 | 8 | use alloc::vec; 9 | 10 | use stylus_sdk::prelude::sol_interface; 11 | sol_interface! { 12 | interface Erc20Interface { 13 | function totalSupply() external view returns (uint256); 14 | function balanceOf(address account) external view returns (uint256); 15 | function transfer(address to, uint256 value) external returns (bool); 16 | function allowance(address owner, address spender) external view returns (uint256); 17 | function approve(address spender, uint256 value) external returns (bool); 18 | function transferFrom(address from, address to, uint256 value) external returns (bool); 19 | } 20 | } 21 | 22 | sol_interface! { 23 | /// Solidity Interface of the ERC-20 Metadata token. 24 | interface IErc20MetadataInterface { 25 | function name() external view returns (string); 26 | function symbol() external view returns (string); 27 | function decimals() external view returns (uint8); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/src/token/erc20/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for the ERC-20 standard. 2 | pub mod safe_erc20; 3 | 4 | pub use safe_erc20::{ISafeErc20, SafeErc20}; 5 | -------------------------------------------------------------------------------- /contracts/src/token/erc721/extensions/mod.rs: -------------------------------------------------------------------------------- 1 | //! Common extensions to the ERC-721 standard. 2 | pub mod burnable; 3 | pub mod consecutive; 4 | pub mod enumerable; 5 | pub mod metadata; 6 | pub mod uri_storage; 7 | pub mod wrapper; 8 | 9 | pub use burnable::IErc721Burnable; 10 | pub use consecutive::Erc721Consecutive; 11 | pub use enumerable::{Erc721Enumerable, IErc721Enumerable}; 12 | pub use metadata::{Erc721Metadata, IErc721Metadata}; 13 | pub use uri_storage::{Erc721UriStorage, IErc721UriStorage}; 14 | pub use wrapper::{Erc721Wrapper, IErc721Wrapper}; 15 | -------------------------------------------------------------------------------- /contracts/src/token/erc721/interface.rs: -------------------------------------------------------------------------------- 1 | //! Solidity Interface of the ERC-721 token. 2 | pub use token::*; 3 | 4 | mod token { 5 | #![allow(missing_docs)] 6 | #![cfg_attr(coverage_nightly, coverage(off))] 7 | use alloc::vec; 8 | 9 | stylus_sdk::prelude::sol_interface! { 10 | interface Erc721Interface { 11 | function balanceOf(address owner) external view returns (uint256 balance); 12 | function ownerOf(uint256 token_id) external view returns (address owner); 13 | function safeTransferFrom(address from, address to, uint256 token_id, bytes calldata data) external; 14 | function transferFrom(address from, address to, uint256 token_id) external; 15 | function approve(address to, uint256 token_id) external; 16 | function setApprovalForAll(address operator, bool approved) external; 17 | function getApproved(uint256 token_id) external view returns (address operator); 18 | function isApprovedForAll(address owner, address operator) external view returns (bool); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/src/token/erc721/receiver.rs: -------------------------------------------------------------------------------- 1 | //! Module with an interface required for smart contract in order to receive 2 | //! ERC-721 token transfers. 3 | #![allow(missing_docs)] 4 | #![cfg_attr(coverage_nightly, coverage(off))] 5 | use alloc::vec; 6 | 7 | use stylus_sdk::prelude::sol_interface; 8 | 9 | sol_interface! { 10 | /// [`super::Erc721`] token receiver interface. 11 | /// 12 | /// Interface for any contract that wants to support safe transfers from 13 | /// [`super::Erc721`] asset contracts. 14 | interface IERC721Receiver { 15 | /// This function is called whenever an [`super::Erc721`] `token_id` 16 | /// token is transferred to this contract via 17 | /// [`super::IErc721::safe_transfer_from`]. 18 | /// 19 | /// It must return its function selector to confirm the token transfer. 20 | /// If any other value is returned or the interface is not implemented 21 | /// by the recipient, the transfer will be reverted. 22 | /// 23 | /// NOTE: To accept the transfer, this must return 24 | /// [`super::RECEIVER_FN_SELECTOR`], or its own function selector. 25 | /// 26 | /// # Arguments 27 | /// 28 | /// * `operator` - Account of the operator. 29 | /// * `from` - Account of the sender. 30 | /// * `token_id` - Token id as a number. 31 | /// * `data` - Additional data with no specified format. 32 | #[allow(missing_docs)] 33 | function onERC721Received( 34 | address operator, 35 | address from, 36 | uint256 token_id, 37 | bytes calldata data 38 | ) external returns (bytes4); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/src/token/mod.rs: -------------------------------------------------------------------------------- 1 | //! Token standards. 2 | pub mod common; 3 | pub mod erc1155; 4 | pub mod erc20; 5 | pub mod erc721; 6 | -------------------------------------------------------------------------------- /contracts/src/utils/cryptography/mod.rs: -------------------------------------------------------------------------------- 1 | //! Smart Contracts with cryptography. 2 | pub mod ecdsa; 3 | pub mod eip712; 4 | -------------------------------------------------------------------------------- /contracts/src/utils/introspection/erc165.rs: -------------------------------------------------------------------------------- 1 | //! Trait and implementation of the ERC-165 standard, as defined in the [ERC]. 2 | //! 3 | //! [ERC]: https://eips.ethereum.org/EIPS/eip-165 4 | 5 | use alloy_primitives::FixedBytes; 6 | use openzeppelin_stylus_proc::interface_id; 7 | 8 | /// Interface of the ERC-165 standard, as defined in the [ERC]. 9 | /// 10 | /// Implementers can declare support of contract interfaces, which others can 11 | /// query. 12 | /// 13 | /// # Example 14 | /// 15 | /// ```rust,ignore 16 | /// impl IErc165 for Erc20 { 17 | /// fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { 18 | /// ::interface_id() == interface_id 19 | /// || ::interface_id() == interface_id 20 | /// } 21 | /// } 22 | /// ``` 23 | /// 24 | /// [ERC]: https://eips.ethereum.org/EIPS/eip-165 25 | #[interface_id] 26 | pub trait IErc165 { 27 | /// Returns true if this contract implements the interface defined by 28 | /// `interface_id`. See the corresponding [ERC] to learn more about how 29 | /// these ids are created. 30 | /// 31 | /// # Arguments 32 | /// 33 | /// * `&self` - Read access to the contract's state. 34 | /// * `interface_id` - The interface identifier, as specified in the [ERC]. 35 | /// 36 | /// [ERC]: https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified 37 | fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool; 38 | } 39 | -------------------------------------------------------------------------------- /contracts/src/utils/introspection/mod.rs: -------------------------------------------------------------------------------- 1 | //! Stylus contract's introspection helpers library. 2 | pub mod erc165; 3 | -------------------------------------------------------------------------------- /contracts/src/utils/math/mod.rs: -------------------------------------------------------------------------------- 1 | //! Math helpers for `alloy` and Solidity storage types. 2 | pub mod alloy; 3 | pub mod storage; 4 | -------------------------------------------------------------------------------- /contracts/src/utils/math/storage/checked.rs: -------------------------------------------------------------------------------- 1 | //! Module with "checked" math on storage values that panics on overflow. 2 | use alloy_primitives::Uint; 3 | use alloy_sol_types::sol_data::{IntBitCount, SupportedInt}; 4 | use stylus_sdk::storage::StorageUint; 5 | 6 | /// Adds value and assign the result to `self`, panicking on overflow. 7 | pub(crate) trait AddAssignChecked { 8 | /// Adds `rhs` and assign the result to `self`, panicking on overflow. 9 | fn add_assign_checked(&mut self, rhs: T, msg: &str); 10 | } 11 | 12 | impl AddAssignChecked> 13 | for StorageUint 14 | where 15 | IntBitCount: SupportedInt, 16 | { 17 | fn add_assign_checked(&mut self, rhs: Uint, msg: &str) { 18 | let new_balance = self.get().checked_add(rhs).expect(msg); 19 | self.set(new_balance); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/src/utils/math/storage/mod.rs: -------------------------------------------------------------------------------- 1 | //! Simple math operations missing in `stylus_sdk::storage`. 2 | mod checked; 3 | mod unchecked; 4 | 5 | pub(crate) use checked::AddAssignChecked; 6 | pub(crate) use unchecked::{AddAssignUnchecked, SubAssignUnchecked}; 7 | -------------------------------------------------------------------------------- /contracts/src/utils/math/storage/unchecked.rs: -------------------------------------------------------------------------------- 1 | /// Module with "unchecked" math on storage values. 2 | use alloy_primitives::Uint; 3 | use alloy_sol_types::sol_data::{IntBitCount, SupportedInt}; 4 | use stylus_sdk::storage::StorageUint; 5 | 6 | /// Adds value and assign the result to `self`, ignoring overflow. 7 | pub(crate) trait AddAssignUnchecked { 8 | /// Adds `rhs` and assign the result to `self`, ignoring overflow. 9 | fn add_assign_unchecked(&mut self, rhs: T); 10 | } 11 | 12 | impl AddAssignUnchecked> 13 | for StorageUint 14 | where 15 | IntBitCount: SupportedInt, 16 | { 17 | fn add_assign_unchecked(&mut self, rhs: Uint) { 18 | let new_balance = self.get() + rhs; 19 | self.set(new_balance); 20 | } 21 | } 22 | 23 | /// Subtract value and assign the result to `self`, ignoring overflow. 24 | pub(crate) trait SubAssignUnchecked { 25 | /// Subtract `rhs` and assign the result to `self`, ignoring overflow. 26 | fn sub_assign_unchecked(&mut self, rhs: T); 27 | } 28 | 29 | impl SubAssignUnchecked> 30 | for StorageUint 31 | where 32 | IntBitCount: SupportedInt, 33 | { 34 | fn sub_assign_unchecked(&mut self, rhs: Uint) { 35 | let new_balance = self.get() - rhs; 36 | self.set(new_balance); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/src/utils/metadata.rs: -------------------------------------------------------------------------------- 1 | //! Common Metadata Smart Contract. 2 | use alloc::{string::String, vec, vec::Vec}; 3 | 4 | use stylus_sdk::{prelude::*, storage::StorageString}; 5 | 6 | /// State of a [`Metadata`] contract. 7 | #[storage] 8 | pub struct Metadata { 9 | /// Token name. 10 | pub(crate) name: StorageString, 11 | /// Token symbol. 12 | pub(crate) symbol: StorageString, 13 | } 14 | 15 | #[public] 16 | impl Metadata { 17 | /// Constructor. 18 | /// 19 | /// # Arguments 20 | /// 21 | /// * `&mut self` - Write access to the contract's state. 22 | /// * `name` - Token name. 23 | /// * `symbol` - Token symbol. 24 | #[constructor] 25 | pub fn constructor(&mut self, name: String, symbol: String) { 26 | self.name.set_str(name); 27 | self.symbol.set_str(symbol); 28 | } 29 | 30 | /// Returns the name of the token. 31 | /// 32 | /// # Arguments 33 | /// 34 | /// * `&self` - Read access to the contract's state. 35 | pub fn name(&self) -> String { 36 | self.name.get_string() 37 | } 38 | 39 | /// Returns the symbol of the token, usually a shorter version of the name. 40 | /// 41 | /// # Arguments 42 | /// 43 | /// * `&self` - Read access to the contract's state. 44 | pub fn symbol(&self) -> String { 45 | self.symbol.get_string() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! Common Smart Contracts utilities. 2 | pub mod cryptography; 3 | pub mod introspection; 4 | pub mod math; 5 | pub mod metadata; 6 | pub mod nonces; 7 | pub mod pausable; 8 | pub mod structs; 9 | 10 | pub use metadata::Metadata; 11 | pub use pausable::{IPausable, Pausable}; 12 | -------------------------------------------------------------------------------- /contracts/src/utils/structs/mod.rs: -------------------------------------------------------------------------------- 1 | //! Solidity storage types used by other contracts. 2 | pub mod bitmap; 3 | pub mod checkpoints; 4 | pub mod enumerable_set; 5 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: contracts-stylus 2 | title: Contracts for Stylus 3 | version: 0.1.1 4 | nav: 5 | - modules/ROOT/nav.adoc 6 | -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Overview] 2 | 3 | * xref:tokens.adoc[Tokens] 4 | ** xref:erc20.adoc[ERC-20] 5 | *** xref:erc20.adoc#erc20-token-extensions[Extensions] 6 | ** xref:erc721.adoc[ERC-721] 7 | *** xref:erc721.adoc#erc721-token-extensions[Extensions] 8 | ** xref:erc1155.adoc[ERC-1155] 9 | *** xref:erc1155.adoc#erc1155-token-extensions[Extensions] 10 | 11 | * xref:access-control.adoc[Access Control] 12 | * xref:crypto.adoc[Cryptography] 13 | * xref:utilities.adoc[Utilities] 14 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/common.adoc: -------------------------------------------------------------------------------- 1 | = Common (Tokens) 2 | 3 | Contracts that are common to multiple token standards. 4 | 5 | == ERC-2981 6 | * *https://eips.ethereum.org/EIPS/eip-2981[ERC-2981]*: standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal support for royalty payments across all NFT marketplaces and ecosystem participants. 7 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/crypto.adoc: -------------------------------------------------------------------------------- 1 | = Crypto 2 | 3 | The OpenZeppelin Rust Contracts provide a crate for common cryptographic procedures in a blockchain environment. The following documents the available functionality. 4 | 5 | == Verifying Merkle Proofs 6 | 7 | Developers can build a Merkle Tree off-chain, which allows for verifying that an element (leaf) is part of a set by using a Merkle Proof. This technique is widely used for creating whitelists (e.g. for airdrops) and other advanced use cases. 8 | 9 | TIP: OpenZeppelin Contracts provides a https://github.com/OpenZeppelin/merkle-tree[JavaScript library] for building trees off-chain and generating proofs. 10 | 11 | https://docs.rs/openzeppelin-crypto/0.2.0-rc.0/openzeppelin_crypto/merkle/struct.Verifier.html[`MerkleProof`] provides: 12 | 13 | * https://docs.rs/openzeppelin-crypto/0.2.0-rc.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify[`verify`] - can prove that some value is part of a https://en.wikipedia.org/wiki/Merkle_tree[Merkle tree]. 14 | 15 | * https://docs.rs/openzeppelin-crypto/0.2.0-rc.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof[`verify_multi_proof`] - can prove multiple values are part of a Merkle tree. 16 | 17 | [source,rust] 18 | ---- 19 | fn verify(&self, proof: Vec, root: B256, leaf: B256) -> bool { 20 | let proof: Vec<[u8; 32]> = proof.into_iter().map(|m| *m).collect(); 21 | Verifier::::verify(&proof, *root, *leaf) 22 | } 23 | ---- 24 | 25 | Note that these functions use `keccak256` as the hashing algorithm, but our library also provides generic counterparts: https://docs.rs/openzeppelin-crypto/0.2.0-rc.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_with_builder[`verify_with_builder`] and https://docs.rs/openzeppelin-crypto/0.2.0-rc.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof_with_builder[`verify_multi_proof_with_builder`]. 26 | 27 | We also provide an adapter https://docs.rs/openzeppelin-crypto/0.2.0-rc.0/openzeppelin_crypto/hash/index.html[`hash`] module to use your own hashers in conjunction with them that resembles Rust's standard library's API. 28 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/erc1155.adoc: -------------------------------------------------------------------------------- 1 | = ERC-1155 2 | 3 | ERC1155 is a novel token standard that aims to take the best from previous standards to create a xref:tokens.adoc#different-kinds-of-tokens[*fungibility-agnostic*] and *gas-efficient* xref:tokens.adoc#but_first_coffee_a_primer_on_token_contracts[token contract]. 4 | 5 | [[erc1155-token-extensions]] 6 | == Extensions 7 | 8 | Additionally, there are multiple custom extensions, including: 9 | 10 | * xref:erc1155-burnable.adoc[ERC-1155 Burnable]: Optional Burnable extension of the ERC-1155 standard. 11 | 12 | * xref:erc1155-metadata-uri.adoc[ERC-1155 Metadata URI]: Optional extension that adds a token metadata URI. 13 | 14 | * xref:erc1155-pausable.adoc[ERC-1155 Pausable]: A primitive to pause contract operation like token transfers, minting and burning. 15 | 16 | * xref:erc1155-uri-storage.adoc[ERC-1155 URI Storage]: A more flexible but more expensive way of storing URI metadata. 17 | 18 | * xref:erc1155-supply.adoc[ERC-1155 Supply]: Extension of the ERC-1155 standard that adds tracking of total supply per token id. 19 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/erc20-burnable.adoc: -------------------------------------------------------------------------------- 1 | = ERC-20 Burnable 2 | 3 | Extension of xref:erc20.adoc[ERC-20] that allows token holders to destroy both their own tokens and those that they have an allowance for, in a way that can be recognized off-chain (via event analysis). 4 | 5 | [[usage]] 6 | == Usage 7 | 8 | In order to make https://docs.rs/openzeppelin-stylus/0.2.0-rc.0/openzeppelin_stylus/token/erc20/extensions/burnable/index.html[`ERC-20 Burnable`] methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: 9 | 10 | [source,rust] 11 | ---- 12 | use openzeppelin_stylus::token::erc20::{ 13 | self, extensions::IErc20Burnable, Erc20, IErc20, 14 | }; 15 | 16 | #[entrypoint] 17 | #[storage] 18 | struct Erc20Example { 19 | erc20: Erc20, 20 | } 21 | 22 | #[public] 23 | #[implements(IErc20, IErc20Burnable)] 24 | impl Erc20Example {} 25 | 26 | 27 | #[public] 28 | impl IErc20 for Erc20Example { 29 | type Error = erc20::Error; 30 | 31 | fn total_supply(&self) -> U256 { 32 | self.erc20.total_supply() 33 | } 34 | 35 | fn balance_of(&self, account: Address) -> U256 { 36 | self.erc20.balance_of(account) 37 | } 38 | 39 | fn transfer( 40 | &mut self, 41 | to: Address, 42 | value: U256, 43 | ) -> Result { 44 | self.erc20.transfer(to, value) 45 | } 46 | 47 | fn allowance(&self, owner: Address, spender: Address) -> U256 { 48 | self.erc20.allowance(owner, spender) 49 | } 50 | 51 | fn approve( 52 | &mut self, 53 | spender: Address, 54 | value: U256, 55 | ) -> Result { 56 | self.erc20.approve(spender, value) 57 | } 58 | 59 | fn transfer_from( 60 | &mut self, 61 | from: Address, 62 | to: Address, 63 | value: U256, 64 | ) -> Result { 65 | self.erc20.transfer_from(from, to, value) 66 | } 67 | } 68 | 69 | 70 | #[public] 71 | impl IErc20Burnable for Erc20Example { 72 | type Error = erc20::Error; 73 | 74 | fn burn(&mut self, value: U256) -> Result<(), erc20::Error> { 75 | // ... 76 | self.erc20.burn(value) 77 | } 78 | 79 | fn burn_from( 80 | &mut self, 81 | account: Address, 82 | value: U256, 83 | ) -> Result<(), erc20::Error> { 84 | // ... 85 | self.erc20.burn_from(account, value) 86 | } 87 | } 88 | ---- 89 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/erc20-flash-mint.adoc: -------------------------------------------------------------------------------- 1 | = ERC-20 Flash Mint 2 | 3 | Extension of xref:erc20.adoc[ERC-20] that provides flash loan support at the token level. 4 | 5 | [[usage]] 6 | == Usage 7 | 8 | In order to make https://docs.rs/openzeppelin-stylus/0.2.0-rc.0/openzeppelin_stylus/token/erc20/extensions/flash_mint/index.html[`ERC-20 Flash Mint`] methods “external” so that other contracts can call them, you need to add the following code to your contract: 9 | 10 | [source,rust] 11 | ---- 12 | use openzeppelin_stylus::token::erc20::{ 13 | extensions::{flash_mint, Erc20FlashMint, IErc3156FlashLender}, 14 | Erc20, IErc20, 15 | }; 16 | 17 | #[entrypoint] 18 | #[storage] 19 | struct Erc20FlashMintExample { 20 | erc20: Erc20, 21 | flash_mint: Erc20FlashMint, 22 | } 23 | 24 | #[public] 25 | #[implements(IErc20, IErc3156FlashLender)] 26 | impl Erc20FlashMintExample {} 27 | 28 | #[public] 29 | impl IErc3156FlashLender for Erc20FlashMintExample { 30 | type Error = flash_mint::Error; 31 | 32 | fn max_flash_loan(&self, token: Address) -> U256 { 33 | self.flash_mint.max_flash_loan(token, &self.erc20) 34 | } 35 | 36 | fn flash_fee( 37 | &self, 38 | token: Address, 39 | value: U256, 40 | ) -> Result { 41 | self.flash_mint.flash_fee(token, value) 42 | } 43 | 44 | fn flash_loan( 45 | &mut self, 46 | receiver: Address, 47 | token: Address, 48 | value: U256, 49 | data: Bytes, 50 | ) -> Result { 51 | self.flash_mint.flash_loan( 52 | receiver, 53 | token, 54 | value, 55 | &data, 56 | &mut self.erc20, 57 | ) 58 | } 59 | } 60 | 61 | #[public] 62 | impl IErc20 for Erc20FlashMintExample { 63 | type Error = flash_mint::Error; 64 | 65 | fn total_supply(&self) -> U256 { 66 | self.erc20.total_supply() 67 | } 68 | 69 | fn balance_of(&self, account: Address) -> U256 { 70 | self.erc20.balance_of(account) 71 | } 72 | 73 | fn transfer( 74 | &mut self, 75 | to: Address, 76 | value: U256, 77 | ) -> Result { 78 | Ok(self.erc20.transfer(to, value)?) 79 | } 80 | 81 | fn allowance(&self, owner: Address, spender: Address) -> U256 { 82 | self.erc20.allowance(owner, spender) 83 | } 84 | 85 | fn approve( 86 | &mut self, 87 | spender: Address, 88 | value: U256, 89 | ) -> Result { 90 | Ok(self.erc20.approve(spender, value)?) 91 | } 92 | 93 | fn transfer_from( 94 | &mut self, 95 | from: Address, 96 | to: Address, 97 | value: U256, 98 | ) -> Result { 99 | Ok(self.erc20.transfer_from(from, to, value)?) 100 | } 101 | } 102 | ---- 103 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/erc20-metadata.adoc: -------------------------------------------------------------------------------- 1 | = ERC-20 Metadata 2 | 3 | Extension of xref:erc20.adoc[ERC-20] that adds the optional metadata functions from the ERC20 standard. 4 | 5 | [[usage]] 6 | == Usage 7 | 8 | In order to make https://docs.rs/openzeppelin-stylus/0.2.0-rc.0/openzeppelin_stylus/token/erc20/extensions/metadata/index.html[`ERC-20 Metadata`] methods “external” so that other contracts can call them, you need to add the following code to your contract: 9 | 10 | [source,rust] 11 | ---- 12 | use openzeppelin_stylus::{ 13 | token::erc20::{ 14 | self, 15 | extensions::{Erc20Metadata, IErc20Metadata}, 16 | Erc20, IErc20, 17 | }, 18 | }; 19 | 20 | #[entrypoint] 21 | #[storage] 22 | struct Erc20Example { 23 | erc20: Erc20, 24 | metadata: Erc20Metadata, 25 | } 26 | 27 | #[public] 28 | #[implements(IErc20, IErc20Metadata, IErc165)] 29 | impl Erc20Example { 30 | #[constructor] 31 | fn constructor(&mut self, name: String, symbol: String) { 32 | self.metadata.constructor(name, symbol); 33 | } 34 | 35 | // ... 36 | } 37 | 38 | #[public] 39 | impl IErc20 for Erc20Example { 40 | type Error = erc20::Error; 41 | 42 | fn total_supply(&self) -> U256 { 43 | self.erc20.total_supply() 44 | } 45 | 46 | fn balance_of(&self, account: Address) -> U256 { 47 | self.erc20.balance_of(account) 48 | } 49 | 50 | fn transfer( 51 | &mut self, 52 | to: Address, 53 | value: U256, 54 | ) -> Result { 55 | self.erc20.transfer(to, value) 56 | } 57 | 58 | fn allowance(&self, owner: Address, spender: Address) -> U256 { 59 | self.erc20.allowance(owner, spender) 60 | } 61 | 62 | fn approve( 63 | &mut self, 64 | spender: Address, 65 | value: U256, 66 | ) -> Result { 67 | self.erc20.approve(spender, value) 68 | } 69 | 70 | fn transfer_from( 71 | &mut self, 72 | from: Address, 73 | to: Address, 74 | value: U256, 75 | ) -> Result { 76 | self.erc20.transfer_from(from, to, value) 77 | } 78 | } 79 | 80 | #[public] 81 | impl IErc20Metadata for Erc20Example { 82 | fn name(&self) -> String { 83 | self.metadata.name() 84 | } 85 | 86 | fn symbol(&self) -> String { 87 | self.metadata.symbol() 88 | } 89 | 90 | fn decimals(&self) -> U8 { 91 | self.metadata.decimals() 92 | } 93 | } 94 | 95 | #[public] 96 | impl IErc165 for Erc20Example { 97 | fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { 98 | self.erc20.supports_interface(interface_id) 99 | || self.metadata.supports_interface(interface_id) 100 | } 101 | } 102 | ---- 103 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/erc20-wrapper.adoc: -------------------------------------------------------------------------------- 1 | = ERC-20 Wrapper 2 | 3 | Extension of the ERC-20 token contract to support token wrapping. 4 | 5 | Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". 6 | This is useful in conjunction with other modules. 7 | 8 | 9 | [[usage]] 10 | == Usage 11 | 12 | In order to make https://docs.rs/openzeppelin-stylus/0.2.0-rc.0/openzeppelin_stylus/token/erc20/extensions/wrapper/index.html[`ERC20Wrapper`] methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: 13 | 14 | [source,rust] 15 | ---- 16 | use openzeppelin_stylus::token::erc20::{ 17 | extensions::{wrapper, Erc20Wrapper, IErc20Wrapper}, 18 | Erc20, IErc20, 19 | }; 20 | use stylus_sdk::{ 21 | alloy_primitives::{Address, U256, U8}, 22 | prelude::*, 23 | }; 24 | 25 | #[entrypoint] 26 | #[storage] 27 | struct Erc20WrapperExample { 28 | erc20: Erc20, 29 | erc20_wrapper: Erc20Wrapper, 30 | } 31 | 32 | #[public] 33 | #[implements(IErc20, IErc20Wrapper)] 34 | impl Erc20WrapperExample { 35 | #[constructor] 36 | fn constructor( 37 | &mut self, 38 | underlying_token: Address, 39 | ) -> Result<(), wrapper::Error> { 40 | self.erc20_wrapper.constructor(underlying_token)? 41 | } 42 | } 43 | 44 | #[public] 45 | impl IErc20Wrapper for Erc20WrapperExample { 46 | type Error = wrapper::Error; 47 | 48 | fn underlying(&self) -> Address { 49 | self.erc20_wrapper.underlying() 50 | } 51 | 52 | fn decimals(&self) -> U8 { 53 | self.erc20_wrapper.decimals() 54 | } 55 | 56 | fn deposit_for( 57 | &mut self, 58 | account: Address, 59 | value: U256, 60 | ) -> Result { 61 | self.erc20_wrapper.deposit_for(account, value, &mut self.erc20) 62 | } 63 | 64 | fn withdraw_to( 65 | &mut self, 66 | account: Address, 67 | value: U256, 68 | ) -> Result { 69 | self.erc20_wrapper.withdraw_to(account, value, &mut self.erc20) 70 | } 71 | } 72 | 73 | #[public] 74 | impl IErc20 for Erc20WrapperExample { 75 | type Error = wrapper::Error; 76 | 77 | fn total_supply(&self) -> U256 { 78 | self.erc20.total_supply() 79 | } 80 | 81 | fn balance_of(&self, account: Address) -> U256 { 82 | self.erc20.balance_of(account) 83 | } 84 | 85 | fn transfer( 86 | &mut self, 87 | to: Address, 88 | value: U256, 89 | ) -> Result { 90 | Ok(self.erc20.transfer(to, value)?) 91 | } 92 | 93 | fn allowance(&self, owner: Address, spender: Address) -> U256 { 94 | self.erc20.allowance(owner, spender) 95 | } 96 | 97 | fn approve( 98 | &mut self, 99 | spender: Address, 100 | value: U256, 101 | ) -> Result { 102 | Ok(self.erc20.approve(spender, value)?) 103 | } 104 | 105 | fn transfer_from( 106 | &mut self, 107 | from: Address, 108 | to: Address, 109 | value: U256, 110 | ) -> Result { 111 | Ok(self.erc20.transfer_from(from, to, value)?) 112 | } 113 | } 114 | ---- 115 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/erc2981.adoc: -------------------------------------------------------------------------------- 1 | = ERC-2981 2 | 3 | ERC-2981 offers a way to keep track of NFT royalties across different platforms. 4 | 5 | Royalty is the amount of money given to the owner of a particular asset (NFT in this case). Full definition: https://en.wikipedia.org/wiki/Royalty_payment [royalty]. 6 | 7 | This makes ERC-2981 useful for keeping track of royalty information across different platforms. It ensures that users don't have to specify this information on every platform, and the information remains transparent on the blockchain. 8 | 9 | == Limitations and Considerations 10 | 11 | - **Marketplace Enforcement**: Some NFT marketplaces may not enforce royalties. 12 | - **Manual Distribution**: On-chain royalty tracking does not mean automatic payouts. 13 | - **Gas Costs**: Extra storage and computations can slightly increase gas fees. 14 | 15 | == Additional Resources 16 | 17 | - ERC-2981 EIP: https://eips.ethereum.org/EIPS/eip-2981[EIP-2981 Specification] 18 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/finance.adoc: -------------------------------------------------------------------------------- 1 | = Finance 2 | 3 | Contracts that include primitives for financial systems. 4 | 5 | == VestingWallet 6 | * xref:vesting-wallet.adoc[VestingWallet] handles the vesting of Ether and ERC-20 7 | tokens for a given beneficiary. Custody of multiple tokens can be given to this 8 | contract, which will release the token to the beneficiary following a given, 9 | customizable, vesting schedule. 10 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | :stylus: https://docs.arbitrum.io/stylus/gentle-introduction[Stylus] 2 | 3 | = Contracts for Stylus 4 | 5 | *A library for secure smart contract development* written in Rust for {stylus}. 6 | 7 | NOTE: You can track our roadmap in our https://github.com/orgs/OpenZeppelin/projects/35[Github Project]. 8 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/tokens.adoc: -------------------------------------------------------------------------------- 1 | = Tokens 2 | 3 | Ah, the "token": blockchain's most powerful and most misunderstood tool. 4 | 5 | A token is a _representation of something in the blockchain_. This something can be money, time, services, shares in a company, a virtual pet, anything. By representing things as tokens, we can allow smart contracts to interact with them, exchange them, create or destroy them. 6 | 7 | [[but_first_coffee_a_primer_on_token_contracts]] 8 | == But First, [strikethrough]#Coffee# a Primer on Token Contracts 9 | 10 | Much of the confusion surrounding tokens comes from two concepts getting mixed up: _token contracts_ and the actual _tokens_. 11 | 12 | A _token contract_ is simply an Ethereum smart contract. "Sending tokens" actually means "calling a method on a smart contract that someone wrote and deployed". At the end of the day, a token contract is not much more than a mapping of addresses to balances, plus some methods to add and subtract from those balances. 13 | 14 | It is these balances that represent the _tokens_ themselves. Someone "has tokens" when their balance in the token contract is non-zero. That's it! These balances could be considered money, experience points in a game, deeds of ownership, or voting rights, and each of these tokens would be stored in different token contracts. 15 | 16 | [[different-kinds-of-tokens]] 17 | == Different Kinds of Tokens 18 | 19 | Note that there's a big difference between having two voting rights and two deeds of ownership: each vote is equal to all others, but houses usually are not! This is called https://en.wikipedia.org/wiki/Fungibility[fungibility]. _Fungible goods_ are equivalent and interchangeable, like Ether, fiat currencies, and voting rights. _Non-fungible_ goods are unique and distinct, like deeds of ownership, or collectibles. 20 | 21 | In a nutshell, when dealing with non-fungibles (like your house) you care about _which ones_ you have, while in fungible assets (like your bank account statement) what matters is _how much_ you have. 22 | 23 | == Standards 24 | 25 | Even though the concept of a token is simple, they have a variety of complexities in the implementation. Because everything in Ethereum is just a smart contract, and there are no rules about what smart contracts have to do, the community has developed a variety of *standards* (called EIPs or ERCs) for documenting how a contract can interoperate with other contracts. 26 | 27 | You've probably heard of the ERC-20, ERC-721 or ERC-1155 token standards, and that's why you're here. Head to our specialized guides to learn more about these: 28 | 29 | * xref:erc20.adoc[ERC-20]: the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. 30 | * xref:erc721.adoc[ERC-721]: the de-facto solution for non-fungible tokens, often used for collectibles and games. 31 | * xref:erc1155.adoc[ERC-1155]: a novel standard for multi-tokens, allowing for a single contract to represent multiple fungible and non-fungible tokens, along with batched operations for increased gas efficiency. 32 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/utilities.adoc: -------------------------------------------------------------------------------- 1 | = Utilities 2 | 3 | The OpenZeppelin Stylus Contracts provides a ton of useful utilities that you can use in your project. 4 | For a complete list, check out the https://docs.rs/openzeppelin-stylus/0.2.0-rc.0/openzeppelin_stylus/utils/index.html[API Reference]. 5 | Here are some of the more popular ones. 6 | 7 | [[introspection]] 8 | == Introspection 9 | 10 | It's frequently helpful to know whether a contract supports an interface you'd like to use. 11 | https://eips.ethereum.org/EIPS/eip-165[`ERC-165`] is a standard that helps do runtime interface detection. 12 | Contracts for Stylus provides helpers for implementing ERC-165 in your contracts: 13 | 14 | * https://docs.rs/openzeppelin-stylus/0.2.0-rc.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html[`IERC165`] — this is the ERC-165 trait that defines https://docs.rs/openzeppelin-stylus/0.2.0-rc.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface[`supportsInterface`]. In order to implement ERC-165 interface detection, you should manually expose https://docs.rs/openzeppelin-stylus/0.2.0-rc.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface[`supportsInterface`] function in your contract. 15 | 16 | [source,rust] 17 | ---- 18 | #[entrypoint] 19 | #[storage] 20 | struct Erc721Example { 21 | erc721: Erc721, 22 | } 23 | 24 | #[public] 25 | #[implements(IErc721, IErc165)] 26 | impl Erc721Example { 27 | // ... 28 | } 29 | 30 | #[public] 31 | impl IErc165 for Erc721Example { 32 | fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { 33 | self.erc721.supports_interface(interface_id) 34 | } 35 | } 36 | 37 | #[public] 38 | impl IErc721 for Erc721Example { 39 | // ... 40 | } 41 | ---- 42 | 43 | [[structures]] 44 | == Structures 45 | 46 | Some use cases require more powerful data structures than arrays and mappings offered natively in alloy and the Stylus sdk. 47 | Contracts for Stylus provides these libraries for enhanced data structure management: 48 | 49 | - https://docs.rs/openzeppelin-stylus/0.2.0-rc.0/openzeppelin_stylus/utils/structs/bitmap/index.html[`BitMaps`]: Store packed booleans in storage. 50 | - https://docs.rs/openzeppelin-stylus/0.2.0-rc.0/openzeppelin_stylus/utils/structs/checkpoints/index.html[`Checkpoints`]: Checkpoint values with built-in lookups. 51 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.1.1", 4 | "scripts": { 5 | "docs": "oz-docs -c .", 6 | "docs:watch": "npm run docs watch", 7 | "prepare-docs": "" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@openzeppelin/docs-utils": "^0.1.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/access-control/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "access-control-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | keccak-const.workspace = true 14 | 15 | [dev-dependencies] 16 | alloy.workspace = true 17 | e2e.workspace = true 18 | tokio.workspace = true 19 | eyre.workspace = true 20 | 21 | [lib] 22 | crate-type = ["lib", "cdylib"] 23 | 24 | [features] 25 | e2e = [] 26 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 27 | 28 | [[bin]] 29 | name = "access-control-example" 30 | path = "src/main.rs" 31 | -------------------------------------------------------------------------------- /examples/access-control/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | access_control_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/access-control/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract AccessControl { 7 | function hasRole(bytes32 role, address account) public view virtual returns (bool hasRole); 8 | function getRoleAdmin(bytes32 role) public view virtual returns (bytes32 role); 9 | function grantRole(bytes32 role, address account) public virtual; 10 | function revokeRole(bytes32 role, address account) public virtual; 11 | function renounceRole(bytes32 role, address callerConfirmation) public virtual; 12 | 13 | function setRoleAdmin(bytes32 role, bytes32 adminRole) public virtual; 14 | 15 | function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address member); 16 | function getRoleMemberCount(bytes32 role) public view virtual returns (uint256 count); 17 | function getRoleMembers(bytes32 role) public view virtual returns (address[] memory members); 18 | 19 | error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); 20 | error AccessControlBadConfirmation(); 21 | 22 | #[derive(Debug, PartialEq)] 23 | event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); 24 | #[derive(Debug, PartialEq)] 25 | event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); 26 | #[derive(Debug, PartialEq)] 27 | event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); 28 | } 29 | ); 30 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # Basic Token Example 2 | 3 | This example showcases an end-to-end user journey of how to write, deploy and 4 | query a smart contract written using our library. 5 | 6 | ## Running the example 7 | 8 | There are two crates in the example: a library crate for our contract 9 | implementation, which is a simple `ERC-20` token extended with `Metadata`, and 10 | a binary crate, which holds the deployment script. 11 | 12 | Before running the example, set the `PRIVATE_KEY` const variable and compile 13 | your contract with: 14 | 15 | ```bash 16 | cargo build --release --target wasm32-unknown-unknown 17 | ``` 18 | 19 | You should now be able to run your contract with: 20 | 21 | ```bash 22 | $ cargo run -p basic-script-example 23 | wasm data fee: Ξ0.000097 24 | init code size: 17.0 KB 25 | deploying to RPC: https://sepolia-rollup.arbitrum.io/rpc 26 | deployed code: 0x7E57f52Bb61174DCE87deB10c4B683a550b39e8F 27 | deployment tx hash: 0xc627970c65caa65b3b87703ecf2d26d83520c3ac570d76c72f2d0a04b9895d91 28 | activating contract: 0x7E57f52Bb61174DCE87deB10c4B683a550b39e8F 29 | activated with 2651090 gas 30 | activation tx hash: 0x88c992c4c6e36fd2f49f2b30ea12412a2d5436bbfe80df8d10606abdb1f3f39d 31 | ``` 32 | 33 | Note that the script asserts that the deployed contract has the correct name and 34 | symbol. 35 | 36 | ## Why two crates? 37 | 38 | This split is necessary because we need to compile the contract to `wasm`, 39 | however, the script depends on `alloy`, which in turn depends on `getrandom` 40 | which is not compatible with wasm targets. 41 | -------------------------------------------------------------------------------- /examples/basic/script/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basic-script-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | basic-example = { path = "../token" } 11 | alloy-primitives.workspace = true 12 | alloy.workspace = true 13 | tokio.workspace = true 14 | e2e.workspace = true 15 | -------------------------------------------------------------------------------- /examples/basic/script/src/main.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | network::EthereumWallet, primitives::Address, providers::ProviderBuilder, 3 | signers::local::PrivateKeySigner, sol, 4 | }; 5 | use e2e::{Account, Constructor}; 6 | 7 | sol!( 8 | #[sol(rpc)] 9 | contract BasicToken { 10 | constructor(string memory name_, string memory symbol_); 11 | 12 | function name() external view returns (string name); 13 | function symbol() external view returns (string symbol); 14 | } 15 | ); 16 | 17 | const RPC_URL: &str = "https://sepolia-rollup.arbitrum.io/rpc"; 18 | const PRIVATE_KEY: &str = "your private key"; 19 | const TOKEN_NAME: &str = "Test Token"; 20 | const TOKEN_SYMBOL: &str = "TTK"; 21 | 22 | #[tokio::main] 23 | async fn main() { 24 | // WARNING: Please use a more secure method for storing your privaket key 25 | // than a string at the top of this file. The following code is for testing 26 | // purposes only. 27 | let signer = PRIVATE_KEY 28 | .parse::() 29 | .expect("should parse the private key"); 30 | let wallet = EthereumWallet::from(signer.clone()); 31 | let rpc_url = RPC_URL.parse().expect("should parse rpc url"); 32 | let provider = ProviderBuilder::new() 33 | .with_recommended_fillers() 34 | .wallet(wallet) 35 | .on_http(rpc_url); 36 | let account = Account { signer, wallet: provider.clone() }; 37 | 38 | let contract_address = deploy(&account).await; 39 | 40 | let contract = BasicToken::new(contract_address, &provider); 41 | 42 | let call_result = contract.name().call().await.unwrap(); 43 | assert_eq!(call_result.name, TOKEN_NAME.to_owned()); 44 | 45 | let call_result = contract.symbol().call().await.unwrap(); 46 | assert_eq!(call_result.symbol, TOKEN_SYMBOL.to_owned()); 47 | } 48 | 49 | /// Deploy a `BasicToken` contract to `RPC_URL` using `cargo-stylus`. 50 | async fn deploy(account: &Account) -> Address { 51 | let manifest_dir = 52 | std::env::current_dir().expect("should get current dir from env"); 53 | 54 | // NOTE: It's expected that you compiled your contract beforehand. 55 | // 56 | // You should run `cargo build --release --target wasm32-unknown-unknown` to 57 | // get a wasm binary at `target/wasm32-unknown-unknown/release/{name}.wasm`. 58 | let wasm_path = manifest_dir 59 | .join("target") 60 | .join("wasm32-unknown-unknown") 61 | .join("release") 62 | .join("basic_example.wasm"); 63 | 64 | let constructor = Constructor { 65 | signature: "constructor(string,string)".to_string(), 66 | args: vec![TOKEN_NAME.to_owned(), TOKEN_SYMBOL.to_owned()], 67 | }; 68 | 69 | let deployer = account.as_deployer().with_constructor(constructor); 70 | let address = deployer 71 | .deploy_wasm(&wasm_path) 72 | .await 73 | .expect("contract should be deployed") 74 | .contract_address; 75 | 76 | address 77 | } 78 | -------------------------------------------------------------------------------- /examples/basic/token/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basic-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [features] 15 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 16 | 17 | [lib] 18 | crate-type = ["lib", "cdylib"] 19 | 20 | [[bin]] 21 | name = "basic-example" 22 | path = "src/main.rs" 23 | -------------------------------------------------------------------------------- /examples/basic/token/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | extern crate alloc; 3 | 4 | use alloc::vec::Vec; 5 | 6 | use openzeppelin_stylus::{ 7 | token::erc20::{ 8 | self, 9 | extensions::{Erc20Metadata, IErc20Metadata}, 10 | Erc20, IErc20, 11 | }, 12 | utils::introspection::erc165::IErc165, 13 | }; 14 | use stylus_sdk::{ 15 | alloy_primitives::{Address, FixedBytes, U256, U8}, 16 | prelude::*, 17 | }; 18 | 19 | #[entrypoint] 20 | #[storage] 21 | struct Erc20Example { 22 | erc20: Erc20, 23 | metadata: Erc20Metadata, 24 | } 25 | 26 | #[public] 27 | #[implements(IErc20, IErc20Metadata, IErc165)] 28 | impl Erc20Example { 29 | #[constructor] 30 | fn constructor(&mut self, name: String, symbol: String) { 31 | self.metadata.constructor(name, symbol); 32 | } 33 | 34 | fn mint( 35 | &mut self, 36 | account: Address, 37 | value: U256, 38 | ) -> Result<(), erc20::Error> { 39 | self.erc20._mint(account, value) 40 | } 41 | } 42 | 43 | #[public] 44 | impl IErc20 for Erc20Example { 45 | type Error = erc20::Error; 46 | 47 | fn total_supply(&self) -> U256 { 48 | self.erc20.total_supply() 49 | } 50 | 51 | fn balance_of(&self, account: Address) -> U256 { 52 | self.erc20.balance_of(account) 53 | } 54 | 55 | fn transfer( 56 | &mut self, 57 | to: Address, 58 | value: U256, 59 | ) -> Result { 60 | self.erc20.transfer(to, value) 61 | } 62 | 63 | fn allowance(&self, owner: Address, spender: Address) -> U256 { 64 | self.erc20.allowance(owner, spender) 65 | } 66 | 67 | fn approve( 68 | &mut self, 69 | spender: Address, 70 | value: U256, 71 | ) -> Result { 72 | self.erc20.approve(spender, value) 73 | } 74 | 75 | fn transfer_from( 76 | &mut self, 77 | from: Address, 78 | to: Address, 79 | value: U256, 80 | ) -> Result { 81 | self.erc20.transfer_from(from, to, value) 82 | } 83 | } 84 | 85 | #[public] 86 | impl IErc20Metadata for Erc20Example { 87 | fn name(&self) -> String { 88 | self.metadata.name() 89 | } 90 | 91 | fn symbol(&self) -> String { 92 | self.metadata.symbol() 93 | } 94 | 95 | fn decimals(&self) -> U8 { 96 | self.metadata.decimals() 97 | } 98 | } 99 | 100 | #[public] 101 | impl IErc165 for Erc20Example { 102 | fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { 103 | self.erc20.supports_interface(interface_id) 104 | || self.metadata.supports_interface(interface_id) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /examples/basic/token/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | basic_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/ecdsa/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ecdsa-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | e2e.workspace = true 17 | tokio.workspace = true 18 | eyre.workspace = true 19 | 20 | [lib] 21 | crate-type = ["lib", "cdylib"] 22 | 23 | [features] 24 | e2e = [] 25 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 26 | 27 | [[bin]] 28 | name = "ecdsa-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/ecdsa/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | extern crate alloc; 3 | 4 | use alloc::vec::Vec; 5 | 6 | use alloy_primitives::{Address, B256}; 7 | use openzeppelin_stylus::utils::cryptography::ecdsa; 8 | use stylus_sdk::prelude::*; 9 | 10 | #[entrypoint] 11 | #[storage] 12 | struct ECDSAExample; 13 | 14 | #[public] 15 | impl ECDSAExample { 16 | fn recover( 17 | &mut self, 18 | hash: B256, 19 | v: u8, 20 | r: B256, 21 | s: B256, 22 | ) -> Result { 23 | ecdsa::recover(self, hash, v, r, s) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/ecdsa/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | ecdsa_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/ecdsa/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract ECDSA { 7 | error ECDSAInvalidSignature(); 8 | error ECDSAInvalidSignatureS(bytes32 s); 9 | 10 | #[derive(Debug)] 11 | function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address recovered); 12 | } 13 | ); 14 | -------------------------------------------------------------------------------- /examples/erc1155-metadata-uri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc1155-metadata-uri-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | e2e.workspace = true 17 | tokio.workspace = true 18 | eyre.workspace = true 19 | 20 | [features] 21 | e2e = [] 22 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 23 | 24 | [lib] 25 | crate-type = ["lib", "cdylib"] 26 | 27 | [[bin]] 28 | name = "erc1155-metadata-uri-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/erc1155-metadata-uri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc1155_metadata_uri_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc1155-metadata-uri/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract Erc1155 { 7 | #[derive(Debug)] 8 | function uri(uint256 id) external view returns (string memory uri); 9 | function setTokenURI(uint256 tokenId, string memory tokenURI) external; 10 | function setBaseURI(string memory tokenURI) external; 11 | 12 | #[derive(Debug, PartialEq)] 13 | event URI(string value, uint256 indexed id); 14 | } 15 | ); 16 | -------------------------------------------------------------------------------- /examples/erc1155-supply/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc1155-supply-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives = { workspace = true, features = ["tiny-keccak"] } 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | eyre.workspace = true 17 | tokio.workspace = true 18 | e2e.workspace = true 19 | 20 | [features] 21 | e2e = [] 22 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 23 | 24 | [lib] 25 | crate-type = ["lib", "cdylib"] 26 | 27 | [[bin]] 28 | name = "erc1155-supply-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/erc1155-supply/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc1155_supply_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc1155-supply/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract Erc1155Supply { 7 | function balanceOf(address account, uint256 id) external view returns (uint256 balance); 8 | #[derive(Debug)] 9 | function balanceOfBatch(address[] accounts, uint256[] ids) external view returns (uint256[] memory balances); 10 | function isApprovedForAll(address account, address operator) external view returns (bool approved); 11 | function setApprovalForAll(address operator, bool approved) external; 12 | function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) external; 13 | function safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) external; 14 | function mint(address to, uint256 id, uint256 amount, bytes memory data) external; 15 | function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external; 16 | function burn(address account, uint256 id, uint256 value) external; 17 | function burnBatch(address account, uint256[] memory ids, uint256[] memory values) external; 18 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 19 | function totalSupply(uint256 id) external view returns (uint256); 20 | function totalSupply() external view returns (uint256); 21 | function exists(uint256 id) external view returns (bool); 22 | 23 | error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); 24 | error ERC1155InvalidOperator(address operator); 25 | error ERC1155InvalidSender(address sender); 26 | error ERC1155InvalidReceiver(address receiver); 27 | error ERC1155MissingApprovalForAll(address operator, address owner); 28 | error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); 29 | 30 | #[derive(Debug, PartialEq)] 31 | event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); 32 | #[derive(Debug, PartialEq)] 33 | event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values); 34 | #[derive(Debug, PartialEq)] 35 | event ApprovalForAll(address indexed account, address indexed operator, bool approved); 36 | } 37 | ); 38 | -------------------------------------------------------------------------------- /examples/erc1155-supply/tests/mock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod receiver; 2 | -------------------------------------------------------------------------------- /examples/erc1155/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc1155-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | e2e.workspace = true 17 | tokio.workspace = true 18 | eyre.workspace = true 19 | 20 | [features] 21 | e2e = [] 22 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 23 | 24 | [lib] 25 | crate-type = ["lib", "cdylib"] 26 | 27 | [[bin]] 28 | name = "erc1155-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/erc1155/src/ERC1155ReceiverMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.21; 4 | 5 | import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/IERC1155Receiver.sol"; 6 | import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/introspection/ERC165.sol"; 7 | 8 | contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { 9 | enum RevertType { 10 | None, 11 | RevertWithoutMessage, 12 | RevertWithMessage, 13 | RevertWithCustomError, 14 | Panic 15 | } 16 | 17 | bytes4 private immutable _recRetval; 18 | bytes4 private immutable _batRetval; 19 | RevertType private immutable _error; 20 | 21 | event Received(address operator, address from, uint256 id, uint256 value, bytes data); 22 | event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data); 23 | error CustomError(bytes4); 24 | 25 | constructor(bytes4 recRetval, bytes4 batRetval, RevertType error) { 26 | _recRetval = recRetval; 27 | _batRetval = batRetval; 28 | _error = error; 29 | } 30 | 31 | function onERC1155Received( 32 | address operator, 33 | address from, 34 | uint256 id, 35 | uint256 value, 36 | bytes calldata data 37 | ) external returns (bytes4) { 38 | if (_error == RevertType.RevertWithoutMessage) { 39 | revert(); 40 | } else if (_error == RevertType.RevertWithMessage) { 41 | revert("ERC1155ReceiverMock: reverting on receive"); 42 | } else if (_error == RevertType.RevertWithCustomError) { 43 | revert CustomError(_recRetval); 44 | } else if (_error == RevertType.Panic) { 45 | uint256 a = uint256(0) / uint256(0); 46 | a; 47 | } 48 | 49 | emit Received(operator, from, id, value, data); 50 | return _recRetval; 51 | } 52 | 53 | function onERC1155BatchReceived( 54 | address operator, 55 | address from, 56 | uint256[] calldata ids, 57 | uint256[] calldata values, 58 | bytes calldata data 59 | ) external returns (bytes4) { 60 | if (_error == RevertType.RevertWithoutMessage) { 61 | revert(); 62 | } else if (_error == RevertType.RevertWithMessage) { 63 | revert("ERC1155ReceiverMock: reverting on batch receive"); 64 | } else if (_error == RevertType.RevertWithCustomError) { 65 | revert CustomError(_recRetval); 66 | } else if (_error == RevertType.Panic) { 67 | uint256 a = uint256(0) / uint256(0); 68 | a; 69 | } 70 | 71 | emit BatchReceived(operator, from, ids, values, data); 72 | return _batRetval; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/erc1155/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc1155_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc1155/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract Erc1155 { 7 | function balanceOf(address account, uint256 id) external view returns (uint256 balance); 8 | #[derive(Debug)] 9 | function balanceOfBatch(address[] accounts, uint256[] ids) external view returns (uint256[] memory balances); 10 | function isApprovedForAll(address account, address operator) external view returns (bool approved); 11 | function setApprovalForAll(address operator, bool approved) external; 12 | function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) external; 13 | function safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory values, bytes memory data) external; 14 | function mint(address to, uint256 id, uint256 amount, bytes memory data) external; 15 | function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external; 16 | function burn(address account, uint256 id, uint256 value) external; 17 | function burnBatch(address account, uint256[] memory ids, uint256[] memory values) external; 18 | 19 | error InvalidReceiverWithReason(string message); 20 | error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); 21 | error ERC1155InvalidOperator(address operator); 22 | error ERC1155InvalidSender(address sender); 23 | error ERC1155InvalidReceiver(address receiver); 24 | error ERC1155MissingApprovalForAll(address operator, address owner); 25 | error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); 26 | 27 | error Error(string message); 28 | error Panic(uint256 code); 29 | 30 | #[derive(Debug, PartialEq)] 31 | event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); 32 | #[derive(Debug, PartialEq)] 33 | event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values); 34 | #[derive(Debug, PartialEq)] 35 | event ApprovalForAll(address indexed account, address indexed operator, bool approved); 36 | } 37 | ); 38 | -------------------------------------------------------------------------------- /examples/erc1155/tests/mock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod receiver; 2 | -------------------------------------------------------------------------------- /examples/erc20-flash-mint/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc20-flash-mint-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus = { workspace = true, features = ["reentrant"] } 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | eyre.workspace = true 17 | tokio.workspace = true 18 | e2e.workspace = true 19 | 20 | [features] 21 | e2e = [] 22 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 23 | 24 | [lib] 25 | crate-type = ["lib", "cdylib"] 26 | 27 | [[bin]] 28 | name = "erc20-flash-mint-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/erc20-flash-mint/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc20_flash_mint_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc20-flash-mint/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract Erc20FlashMint { 7 | function totalSupply() external view returns (uint256 totalSupply); 8 | function balanceOf(address account) external view returns (uint256 balance); 9 | function transfer(address recipient, uint256 amount) external returns (bool); 10 | function allowance(address owner, address spender) external view returns (uint256 allowance); 11 | function approve(address spender, uint256 amount) external returns (bool); 12 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 13 | 14 | function mint(address account, uint256 amount) external; 15 | 16 | function maxFlashLoan(address token) external view returns (uint256 maxLoan); 17 | #[derive(Debug)] 18 | function flashFee(address token, uint256 amount) external view returns (uint256 fee); 19 | function flashLoan(address receiver, address token, uint256 amount, bytes calldata data) external returns (bool); 20 | 21 | function setFlashFeeReceiver(address newReceiver) external; 22 | function setFlashFeeValue(uint256 newValue) external; 23 | 24 | error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); 25 | error ERC20InvalidSender(address sender); 26 | error ERC20InvalidReceiver(address receiver); 27 | error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); 28 | error ERC20InvalidSpender(address spender); 29 | 30 | error ERC3156UnsupportedToken(address token); 31 | error ERC3156ExceededMaxLoan(uint256 maxLoan); 32 | error ERC3156InvalidReceiver(address receiver); 33 | 34 | #[derive(Debug, PartialEq)] 35 | event Transfer(address indexed from, address indexed to, uint256 value); 36 | #[derive(Debug, PartialEq)] 37 | event Approval(address indexed owner, address indexed spender, uint256 value); 38 | } 39 | ); 40 | -------------------------------------------------------------------------------- /examples/erc20-flash-mint/tests/mock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod borrower; 2 | -------------------------------------------------------------------------------- /examples/erc20-permit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc20-permit-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | keccak-const.workspace = true 14 | 15 | [dev-dependencies] 16 | alloy.workspace = true 17 | eyre.workspace = true 18 | tokio.workspace = true 19 | e2e.workspace = true 20 | 21 | [features] 22 | e2e = [] 23 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 24 | 25 | [lib] 26 | crate-type = ["lib", "cdylib"] 27 | 28 | [[bin]] 29 | name = "erc20-permit-example" 30 | path = "src/main.rs" 31 | -------------------------------------------------------------------------------- /examples/erc20-permit/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc20_permit_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc20-permit/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(clippy::too_many_arguments)] 3 | use alloy::sol; 4 | 5 | sol!( 6 | #[sol(rpc)] 7 | contract Erc20Permit { 8 | function totalSupply() external view returns (uint256 totalSupply); 9 | function balanceOf(address account) external view returns (uint256 balance); 10 | function transfer(address recipient, uint256 amount) external returns (bool); 11 | function allowance(address owner, address spender) external view returns (uint256 allowance); 12 | function approve(address spender, uint256 amount) external returns (bool); 13 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 14 | 15 | function mint(address account, uint256 amount) external; 16 | 17 | function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; 18 | function nonces(address owner) external view returns (uint256 nonce); 19 | function DOMAIN_SEPARATOR() external view returns (bytes32 domainSeparator); 20 | 21 | error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); 22 | error ERC20InvalidSender(address sender); 23 | error ERC20InvalidReceiver(address receiver); 24 | error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); 25 | error ERC20InvalidSpender(address spender); 26 | 27 | error ERC2612ExpiredSignature(uint256 deadline); 28 | error ERC2612InvalidSigner(address signer, address owner); 29 | 30 | #[derive(Debug, PartialEq)] 31 | event Transfer(address indexed from, address indexed to, uint256 value); 32 | #[derive(Debug, PartialEq)] 33 | event Approval(address indexed owner, address indexed spender, uint256 value); 34 | } 35 | ); 36 | -------------------------------------------------------------------------------- /examples/erc20-wrapper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc20-wrapper-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | eyre.workspace = true 17 | tokio.workspace = true 18 | e2e.workspace = true 19 | 20 | [features] 21 | e2e = [] 22 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 23 | 24 | [lib] 25 | crate-type = ["lib", "cdylib"] 26 | 27 | [[bin]] 28 | name = "erc20-wrapper-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/erc20-wrapper/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | extern crate alloc; 3 | 4 | use alloc::{vec, vec::Vec}; 5 | 6 | use openzeppelin_stylus::token::erc20::{ 7 | extensions::{wrapper, Erc20Wrapper, IErc20Wrapper}, 8 | Erc20, IErc20, 9 | }; 10 | use stylus_sdk::{ 11 | alloy_primitives::{Address, U256, U8}, 12 | prelude::*, 13 | }; 14 | 15 | #[entrypoint] 16 | #[storage] 17 | struct Erc20WrapperExample { 18 | erc20: Erc20, 19 | erc20_wrapper: Erc20Wrapper, 20 | } 21 | 22 | #[public] 23 | #[implements(IErc20, IErc20Wrapper)] 24 | impl Erc20WrapperExample { 25 | #[constructor] 26 | fn constructor( 27 | &mut self, 28 | underlying_token: Address, 29 | decimals: U8, 30 | ) -> Result<(), wrapper::Error> { 31 | self.erc20_wrapper.constructor(underlying_token)?; 32 | self.erc20_wrapper.underlying_decimals.set(decimals); 33 | Ok(()) 34 | } 35 | } 36 | 37 | #[public] 38 | impl IErc20 for Erc20WrapperExample { 39 | type Error = wrapper::Error; 40 | 41 | fn total_supply(&self) -> U256 { 42 | self.erc20.total_supply() 43 | } 44 | 45 | fn balance_of(&self, account: Address) -> U256 { 46 | self.erc20.balance_of(account) 47 | } 48 | 49 | fn transfer( 50 | &mut self, 51 | to: Address, 52 | value: U256, 53 | ) -> Result { 54 | Ok(self.erc20.transfer(to, value)?) 55 | } 56 | 57 | fn allowance(&self, owner: Address, spender: Address) -> U256 { 58 | self.erc20.allowance(owner, spender) 59 | } 60 | 61 | fn approve( 62 | &mut self, 63 | spender: Address, 64 | value: U256, 65 | ) -> Result { 66 | Ok(self.erc20.approve(spender, value)?) 67 | } 68 | 69 | fn transfer_from( 70 | &mut self, 71 | from: Address, 72 | to: Address, 73 | value: U256, 74 | ) -> Result { 75 | Ok(self.erc20.transfer_from(from, to, value)?) 76 | } 77 | } 78 | 79 | #[public] 80 | impl IErc20Wrapper for Erc20WrapperExample { 81 | type Error = wrapper::Error; 82 | 83 | fn underlying(&self) -> Address { 84 | self.erc20_wrapper.underlying() 85 | } 86 | 87 | fn decimals(&self) -> U8 { 88 | self.erc20_wrapper.decimals() 89 | } 90 | 91 | fn deposit_for( 92 | &mut self, 93 | account: Address, 94 | value: U256, 95 | ) -> Result { 96 | self.erc20_wrapper.deposit_for(account, value, &mut self.erc20) 97 | } 98 | 99 | fn withdraw_to( 100 | &mut self, 101 | account: Address, 102 | value: U256, 103 | ) -> Result { 104 | self.erc20_wrapper.withdraw_to(account, value, &mut self.erc20) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /examples/erc20-wrapper/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc20_wrapper_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc20-wrapper/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(clippy::too_many_arguments)] 3 | use alloy::sol; 4 | 5 | sol!( 6 | #[sol(rpc)] 7 | contract Erc20Wrapper { 8 | function totalSupply() external view returns (uint256 totalSupply); 9 | function balanceOf(address account) external view returns (uint256 balance); 10 | function transfer(address recipient, uint256 amount) external returns (bool); 11 | function allowance(address owner, address spender) external view returns (uint256 allowance); 12 | function approve(address spender, uint256 amount) external returns (bool); 13 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 14 | 15 | #[derive(Debug)] 16 | function decimals() external view returns (uint8 decimals); 17 | #[derive(Debug)] 18 | function underlying() external view returns (address underlying); 19 | #[derive(Debug)] 20 | function depositFor(address account, uint256 value) external returns (bool); 21 | #[derive(Debug)] 22 | function withdrawTo(address account, uint256 value) external returns (bool); 23 | 24 | error ERC20InvalidUnderlying(address token); 25 | error ERC20InvalidSender(address sender); 26 | error ERC20InvalidReceiver(address receiver); 27 | 28 | } 29 | 30 | contract Erc20 { 31 | #[derive(Debug, PartialEq)] 32 | event Transfer(address indexed from, address indexed to, uint256 value); 33 | #[derive(Debug, PartialEq)] 34 | event Approval(address indexed owner, address indexed spender, uint256 value); 35 | 36 | error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); 37 | } 38 | 39 | #[sol(rpc)] 40 | contract SafeErc20 { 41 | error SafeErc20FailedOperation(address token); 42 | } 43 | ); 44 | -------------------------------------------------------------------------------- /examples/erc20-wrapper/tests/mock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod erc20; 2 | -------------------------------------------------------------------------------- /examples/erc20/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc20-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | eyre.workspace = true 17 | tokio.workspace = true 18 | e2e.workspace = true 19 | 20 | [features] 21 | e2e = [] 22 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 23 | 24 | [lib] 25 | crate-type = ["lib", "cdylib"] 26 | 27 | [[bin]] 28 | name = "erc20-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/erc20/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc20_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc20/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(clippy::too_many_arguments)] 3 | use alloy::sol; 4 | 5 | sol!( 6 | #[sol(rpc)] 7 | contract Erc20 { 8 | function name() external view returns (string name); 9 | function symbol() external view returns (string symbol); 10 | function decimals() external view returns (uint8 decimals); 11 | function totalSupply() external view returns (uint256 totalSupply); 12 | function balanceOf(address account) external view returns (uint256 balance); 13 | function transfer(address recipient, uint256 amount) external returns (bool); 14 | function allowance(address owner, address spender) external view returns (uint256 allowance); 15 | function approve(address spender, uint256 amount) external returns (bool); 16 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 17 | 18 | function cap() public view virtual returns (uint256 cap); 19 | 20 | function mint(address account, uint256 amount) external; 21 | function burn(uint256 amount) external; 22 | function burnFrom(address account, uint256 amount) external; 23 | 24 | function paused() external view returns (bool paused); 25 | function pause() external; 26 | function unpause() external; 27 | 28 | function supportsInterface(bytes4 interface_id) external view returns (bool supportsInterface); 29 | 30 | error EnforcedPause(); 31 | error ExpectedPause(); 32 | 33 | error ERC20ExceededCap(uint256 increased_supply, uint256 cap); 34 | error ERC20InvalidCap(uint256 cap); 35 | 36 | error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); 37 | error ERC20InvalidSender(address sender); 38 | error ERC20InvalidReceiver(address receiver); 39 | error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); 40 | error ERC20InvalidSpender(address spender); 41 | 42 | #[derive(Debug, PartialEq)] 43 | event Transfer(address indexed from, address indexed to, uint256 value); 44 | #[derive(Debug, PartialEq)] 45 | event Approval(address indexed owner, address indexed spender, uint256 value); 46 | 47 | #[derive(Debug, PartialEq)] 48 | event Paused(address account); 49 | #[derive(Debug, PartialEq)] 50 | event Unpaused(address account); 51 | } 52 | ); 53 | -------------------------------------------------------------------------------- /examples/erc4626/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc4626-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | eyre.workspace = true 17 | tokio.workspace = true 18 | e2e.workspace = true 19 | 20 | [features] 21 | e2e = [] 22 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 23 | 24 | [lib] 25 | crate-type = ["lib", "cdylib"] 26 | 27 | [[bin]] 28 | name = "erc4626-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/erc4626/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc4626_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc4626/tests/mock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod erc20; 2 | pub mod erc20_failing_transfer; 3 | -------------------------------------------------------------------------------- /examples/erc721-consecutive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc721-consecutive-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | alloy-sol-types.workspace = true 13 | stylus-sdk.workspace = true 14 | 15 | [dev-dependencies] 16 | alloy.workspace = true 17 | e2e.workspace = true 18 | tokio.workspace = true 19 | eyre.workspace = true 20 | rand.workspace = true 21 | 22 | [features] 23 | e2e = [] 24 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 25 | 26 | [lib] 27 | crate-type = ["lib", "cdylib"] 28 | 29 | [[bin]] 30 | name = "erc721-consecutive-example" 31 | path = "src/main.rs" 32 | -------------------------------------------------------------------------------- /examples/erc721-consecutive/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc721_consecutive_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc721-consecutive/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract Erc721 { 7 | #[derive(Debug)] 8 | function balanceOf(address owner) external view returns (uint256 balance); 9 | #[derive(Debug)] 10 | function ownerOf(uint256 tokenId) external view returns (address ownerOf); 11 | function safeTransferFrom(address from, address to, uint256 tokenId) external; 12 | function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; 13 | function transferFrom(address from, address to, uint256 tokenId) external; 14 | function approve(address to, uint256 tokenId) external; 15 | function setApprovalForAll(address operator, bool approved) external; 16 | function getApproved(uint256 tokenId) external view returns (address); 17 | function isApprovedForAll(address owner, address operator) external view returns (bool); 18 | function mint(address to, uint256 tokenId) external; 19 | 20 | function burn(uint256 tokenId) external; 21 | 22 | error ERC721InvalidOwner(address owner); 23 | error ERC721NonexistentToken(uint256 tokenId); 24 | error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); 25 | error ERC721InvalidSender(address sender); 26 | error ERC721InvalidReceiver(address receiver); 27 | error ERC721InsufficientApproval(address operator, uint256 tokenId); 28 | error ERC721InvalidApprover(address approver); 29 | error ERC721InvalidOperator(address operator); 30 | 31 | error ERC721ForbiddenBatchMint(); 32 | error ERC721ExceededMaxBatchMint(uint256 batchSize, uint256 maxBatch); 33 | error ERC721ForbiddenMint(); 34 | error ERC721ForbiddenBatchBurn(); 35 | 36 | #[derive(Debug, PartialEq)] 37 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 38 | 39 | #[derive(Debug, PartialEq)] 40 | event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 41 | 42 | #[derive(Debug, PartialEq)] 43 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 44 | 45 | #[derive(Debug, PartialEq)] 46 | event ConsecutiveTransfer( 47 | uint256 indexed fromTokenId, 48 | uint256 toTokenId, 49 | address indexed fromAddress, 50 | address indexed toAddress 51 | ); 52 | } 53 | ); 54 | -------------------------------------------------------------------------------- /examples/erc721-metadata/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc721-metadata-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | e2e.workspace = true 17 | tokio.workspace = true 18 | eyre.workspace = true 19 | rand.workspace = true 20 | 21 | [features] 22 | e2e = [] 23 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 24 | 25 | [lib] 26 | crate-type = ["lib", "cdylib"] 27 | 28 | [[bin]] 29 | name = "erc721-metadata-example" 30 | path = "src/main.rs" 31 | -------------------------------------------------------------------------------- /examples/erc721-metadata/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc721_metadata_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc721-metadata/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract Erc721 { 7 | function approve(address to, uint256 tokenId) external; 8 | #[derive(Debug)] 9 | function balanceOf(address owner) external view returns (uint256 balance); 10 | #[derive(Debug)] 11 | function getApproved(uint256 tokenId) external view returns (address approved); 12 | #[derive(Debug)] 13 | function isApprovedForAll(address owner, address operator) external view returns (bool approved); 14 | #[derive(Debug)] 15 | function ownerOf(uint256 tokenId) external view returns (address ownerOf); 16 | function safeTransferFrom(address from, address to, uint256 tokenId) external; 17 | function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; 18 | function setApprovalForAll(address operator, bool approved) external; 19 | function transferFrom(address from, address to, uint256 tokenId) external; 20 | function mint(address to, uint256 tokenId) external; 21 | function burn(uint256 tokenId) external; 22 | function name() external view returns (string memory name); 23 | function symbol() external view returns (string memory symbol); 24 | #[derive(Debug)] 25 | function tokenURI(uint256 tokenId) external view returns (string memory tokenURI); 26 | function setTokenURI(uint256 tokenId, string memory tokenURI) external; 27 | function supportsInterface(bytes4 interfaceId) external pure returns (bool); 28 | 29 | error ERC721InvalidOwner(address owner); 30 | error ERC721NonexistentToken(uint256 tokenId); 31 | error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); 32 | error ERC721InvalidSender(address sender); 33 | error ERC721InvalidReceiver(address receiver); 34 | error ERC721InsufficientApproval(address operator, uint256 tokenId); 35 | error ERC721InvalidApprover(address approver); 36 | error ERC721InvalidOperator(address operator); 37 | 38 | #[derive(Debug, PartialEq)] 39 | event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 40 | #[derive(Debug, PartialEq)] 41 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 42 | #[derive(Debug, PartialEq)] 43 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 44 | #[derive(Debug, PartialEq)] 45 | event MetadataUpdate(uint256 tokenId); 46 | } 47 | ); 48 | -------------------------------------------------------------------------------- /examples/erc721-wrapper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc721-wrapper-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | eyre.workspace = true 17 | tokio.workspace = true 18 | e2e.workspace = true 19 | 20 | [features] 21 | e2e = [] 22 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 23 | 24 | [lib] 25 | crate-type = ["lib", "cdylib"] 26 | 27 | [[bin]] 28 | name = "erc721-wrapper-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/erc721-wrapper/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc721_wrapper_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc721-wrapper/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract Erc721Wrapper { 7 | #[derive(Debug)] 8 | function balanceOf(address owner) external view returns (uint256 balance); 9 | #[derive(Debug)] 10 | function underlying() external view returns (address underlying); 11 | #[derive(Debug)] 12 | function ownerOf(uint256 tokenId) external view returns (address owner); 13 | #[derive(Debug)] 14 | function depositFor(address account, uint256[] memory tokenIds) external returns (bool); 15 | #[derive(Debug)] 16 | function withdrawTo(address account, uint256[] memory tokenIds) external returns (bool); 17 | 18 | 19 | #[derive(Debug, PartialEq)] 20 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 21 | 22 | error ERC721NonexistentToken(uint256 tokenId); 23 | } 24 | 25 | contract Erc721 { 26 | function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; 27 | 28 | #[derive(Debug)] 29 | function ownerOf(uint256 tokenId) external view returns (address owner); 30 | 31 | #[derive(Debug, PartialEq)] 32 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /examples/erc721-wrapper/tests/mock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod erc721; 2 | -------------------------------------------------------------------------------- /examples/erc721/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "erc721-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | e2e.workspace = true 17 | tokio.workspace = true 18 | eyre.workspace = true 19 | rand.workspace = true 20 | 21 | [features] 22 | e2e = [] 23 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 24 | 25 | [lib] 26 | crate-type = ["lib", "cdylib"] 27 | 28 | [[bin]] 29 | name = "erc721-example" 30 | path = "src/main.rs" 31 | -------------------------------------------------------------------------------- /examples/erc721/src/ERC721ReceiverMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.21; 4 | 5 | import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/token/ERC721/IERC721Receiver.sol"; 6 | 7 | contract ERC721ReceiverMock is IERC721Receiver { 8 | enum RevertType { 9 | None, 10 | RevertWithoutMessage, 11 | RevertWithMessage, 12 | RevertWithCustomError, 13 | Panic 14 | } 15 | 16 | bytes4 private immutable _retval; 17 | RevertType private immutable _error; 18 | 19 | event Received(address operator, address from, uint256 tokenId, bytes data); 20 | 21 | error CustomError(bytes4); 22 | 23 | constructor(bytes4 retval, RevertType error) { 24 | _retval = retval; 25 | _error = error; 26 | } 27 | 28 | function onERC721Received( 29 | address operator, 30 | address from, 31 | uint256 tokenId, 32 | bytes memory data 33 | ) public returns (bytes4) { 34 | if (_error == RevertType.RevertWithoutMessage) { 35 | revert(); 36 | } else if (_error == RevertType.RevertWithMessage) { 37 | revert("ERC721ReceiverMock: reverting"); 38 | } else if (_error == RevertType.RevertWithCustomError) { 39 | revert CustomError(_retval); 40 | } else if (_error == RevertType.Panic) { 41 | uint256 a = uint256(0) / uint256(0); 42 | a; 43 | } 44 | 45 | emit Received(operator, from, tokenId, data); 46 | return _retval; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/erc721/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | erc721_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/erc721/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract Erc721 { 7 | function approve(address to, uint256 tokenId) external; 8 | #[derive(Debug)] 9 | function balanceOf(address owner) external view returns (uint256 balance); 10 | #[derive(Debug)] 11 | function getApproved(uint256 tokenId) external view returns (address approved); 12 | #[derive(Debug)] 13 | function isApprovedForAll(address owner, address operator) external view returns (bool approved); 14 | #[derive(Debug)] 15 | function ownerOf(uint256 tokenId) external view returns (address ownerOf); 16 | function safeTransferFrom(address from, address to, uint256 tokenId) external; 17 | function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; 18 | function setApprovalForAll(address operator, bool approved) external; 19 | function totalSupply() external view returns (uint256 totalSupply); 20 | function transferFrom(address from, address to, uint256 tokenId) external; 21 | function safeMint(address to, uint256 tokenId, bytes calldata data) external; 22 | function mint(address to, uint256 tokenId) external; 23 | function burn(uint256 tokenId) external; 24 | 25 | #[derive(Debug)] 26 | function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); 27 | #[derive(Debug)] 28 | function tokenByIndex(uint256 index) external view returns (uint256 tokenId); 29 | 30 | function supportsInterface(bytes4 interface_id) external view returns (bool supportsInterface); 31 | 32 | error Error(string message); 33 | error Panic(uint256 code); 34 | 35 | error InvalidReceiverWithReason(string message); 36 | error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); 37 | error ERC721InsufficientApproval(address operator, uint256 tokenId); 38 | error ERC721InvalidApprover(address approver); 39 | error ERC721InvalidOperator(address operator); 40 | error ERC721InvalidOwner(address owner); 41 | error ERC721InvalidReceiver(address receiver); 42 | error ERC721InvalidSender(address sender); 43 | error ERC721NonexistentToken(uint256 tokenId); 44 | error ERC721OutOfBoundsIndex(address owner, uint256 index); 45 | error ERC721EnumerableForbiddenBatchMint(); 46 | 47 | #[derive(Debug, PartialEq)] 48 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 49 | #[derive(Debug, PartialEq)] 50 | event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 51 | #[derive(Debug, PartialEq)] 52 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 53 | } 54 | ); 55 | -------------------------------------------------------------------------------- /examples/erc721/tests/mock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod receiver; 2 | -------------------------------------------------------------------------------- /examples/merkle-proofs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "merkle-proofs-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-crypto.workspace = true 11 | alloy-primitives.workspace = true 12 | alloy-sol-types.workspace = true 13 | stylus-sdk.workspace = true 14 | 15 | [features] 16 | e2e = [] 17 | export-abi = ["stylus-sdk/export-abi"] 18 | 19 | [lib] 20 | crate-type = ["lib", "cdylib"] 21 | 22 | [[bin]] 23 | name = "merkle-proofs-example" 24 | path = "src/main.rs" 25 | -------------------------------------------------------------------------------- /examples/merkle-proofs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | extern crate alloc; 3 | 4 | use alloc::vec::Vec; 5 | 6 | use alloy_primitives::B256; 7 | use openzeppelin_crypto::{ 8 | merkle::{self, Verifier}, 9 | KeccakBuilder, 10 | }; 11 | use stylus_sdk::{alloy_sol_types::sol, prelude::*}; 12 | 13 | sol! { 14 | error MerkleProofInvalidMultiProofLength(); 15 | error MerkleProofInvalidRootChild(); 16 | error MerkleProofInvalidTotalHashes(); 17 | error MerkleProofNoLeaves(); 18 | } 19 | 20 | #[derive(SolidityError)] 21 | enum VerifierError { 22 | InvalidProofLength(MerkleProofInvalidMultiProofLength), 23 | InvalidRootChild(MerkleProofInvalidRootChild), 24 | InvalidTotalHashes(MerkleProofInvalidTotalHashes), 25 | NoLeaves(MerkleProofNoLeaves), 26 | } 27 | 28 | impl core::convert::From for VerifierError { 29 | fn from(value: merkle::MultiProofError) -> Self { 30 | match value { 31 | merkle::MultiProofError::InvalidProofLength => { 32 | VerifierError::InvalidProofLength( 33 | MerkleProofInvalidMultiProofLength {}, 34 | ) 35 | } 36 | merkle::MultiProofError::InvalidRootChild => { 37 | VerifierError::InvalidRootChild(MerkleProofInvalidRootChild {}) 38 | } 39 | merkle::MultiProofError::InvalidTotalHashes => { 40 | VerifierError::InvalidTotalHashes( 41 | MerkleProofInvalidTotalHashes {}, 42 | ) 43 | } 44 | merkle::MultiProofError::NoLeaves => { 45 | VerifierError::NoLeaves(MerkleProofNoLeaves {}) 46 | } 47 | } 48 | } 49 | } 50 | 51 | #[entrypoint] 52 | #[storage] 53 | struct VerifierContract; 54 | 55 | #[public] 56 | impl VerifierContract { 57 | fn verify(&self, proof: Vec, root: B256, leaf: B256) -> bool { 58 | let proof: Vec<[u8; 32]> = proof.into_iter().map(|m| *m).collect(); 59 | Verifier::::verify(&proof, *root, *leaf) 60 | } 61 | 62 | fn verify_multi_proof( 63 | &self, 64 | proof: Vec, 65 | proof_flags: Vec, 66 | root: B256, 67 | leaves: Vec, 68 | ) -> Result { 69 | let proof: Vec<[u8; 32]> = proof.into_iter().map(|m| *m).collect(); 70 | let leaves: Vec<[u8; 32]> = leaves.into_iter().map(|m| *m).collect(); 71 | Ok(Verifier::::verify_multi_proof( 72 | &proof, 73 | &proof_flags, 74 | *root, 75 | &leaves, 76 | )?) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/merkle-proofs/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | merkle_proofs_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/ownable-two-step/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ownable-two-step-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | e2e.workspace = true 17 | tokio.workspace = true 18 | eyre.workspace = true 19 | 20 | [lib] 21 | crate-type = ["lib", "cdylib"] 22 | 23 | [features] 24 | e2e = [] 25 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 26 | 27 | [[bin]] 28 | name = "ownable-two-step-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/ownable-two-step/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | ownable_two_step_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/ownable-two-step/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract Ownable2Step { 7 | error OwnableUnauthorizedAccount(address account); 8 | error OwnableInvalidOwner(address owner); 9 | 10 | #[derive(Debug, PartialEq)] 11 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 12 | #[derive(Debug, PartialEq)] 13 | event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); 14 | 15 | function owner() public view virtual returns (address owner); 16 | function pendingOwner() public view returns (address pendingOwner); 17 | function renounceOwnership() public virtual onlyOwner; 18 | function transferOwnership(address newOwner) public virtual; 19 | function acceptOwnership() public virtual; 20 | } 21 | ); 22 | -------------------------------------------------------------------------------- /examples/ownable/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ownable-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | e2e.workspace = true 17 | tokio.workspace = true 18 | eyre.workspace = true 19 | 20 | [lib] 21 | crate-type = ["lib", "cdylib"] 22 | 23 | [features] 24 | e2e = [] 25 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 26 | 27 | [[bin]] 28 | name = "ownable-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/ownable/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | ownable_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/ownable/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract Ownable { 7 | error OwnableUnauthorizedAccount(address account); 8 | error OwnableInvalidOwner(address owner); 9 | 10 | #[derive(Debug, PartialEq)] 11 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 12 | 13 | function owner() public view virtual returns (address owner); 14 | function renounceOwnership() public virtual onlyOwner; 15 | function transferOwnership(address newOwner) public virtual; 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /examples/pedersen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pedersen-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-crypto.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | e2e.workspace = true 17 | tokio.workspace = true 18 | eyre.workspace = true 19 | 20 | [lib] 21 | crate-type = ["lib", "cdylib"] 22 | 23 | [features] 24 | e2e = [] 25 | export-abi = ["stylus-sdk/export-abi"] 26 | 27 | [[bin]] 28 | name = "pedersen-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/pedersen/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | extern crate alloc; 3 | 4 | use alloc::vec::Vec; 5 | 6 | use openzeppelin_crypto::{ 7 | arithmetic::{uint::U256, BigInteger}, 8 | field::prime::PrimeField, 9 | pedersen::{ 10 | instance::starknet::{StarknetCurveConfig, StarknetPedersenParams}, 11 | Pedersen, 12 | }, 13 | }; 14 | use stylus_sdk::prelude::*; 15 | 16 | #[entrypoint] 17 | #[storage] 18 | struct PedersenExample; 19 | 20 | #[public] 21 | impl PedersenExample { 22 | fn hash( 23 | &mut self, 24 | inputs: [alloy_primitives::U256; 2], 25 | ) -> alloy_primitives::U256 { 26 | let hasher = 27 | Pedersen::::new(); 28 | 29 | let inputs: Vec = inputs 30 | .iter() 31 | .map(|x| U256::from_bytes_le(&x.to_le_bytes_vec())) 32 | .collect(); 33 | 34 | let hash = hasher.hash(inputs[0].into(), inputs[1].into()); 35 | let hash = hash.expect("Failed to hash").into_bigint().into_bytes_le(); 36 | 37 | alloy_primitives::U256::from_le_slice(&hash) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/pedersen/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | pedersen_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/pedersen/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract PedersenExample { 7 | #[derive(Debug)] 8 | function hash(uint[2] memory inputs) external view returns (uint hash); 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /examples/pedersen/tests/pedersen.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "e2e")] 2 | 3 | use e2e::Account; 4 | use eyre::Result; 5 | use openzeppelin_crypto::arithmetic::{ 6 | uint::{from_str_hex, U256}, 7 | BigInteger, 8 | }; 9 | 10 | use crate::abi::PedersenExample; 11 | 12 | mod abi; 13 | 14 | // ============================================================================ 15 | // Integration Tests: Pedersen 16 | // ============================================================================ 17 | 18 | fn to_alloy_u256(value: &U256) -> alloy_primitives::U256 { 19 | alloy_primitives::U256::from_le_slice(&value.into_bytes_le()) 20 | } 21 | 22 | #[e2e::test] 23 | async fn pedersen_works(alice: Account) -> Result<()> { 24 | let input_1 = to_alloy_u256(&from_str_hex( 25 | "3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb", 26 | )); 27 | let input_2 = to_alloy_u256(&from_str_hex( 28 | "208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a", 29 | )); 30 | 31 | let expected = to_alloy_u256(&from_str_hex( 32 | "30e480bed5fe53fa909cc0f8c4d99b8f9f2c016be4c41e13a4848797979c662", 33 | )); 34 | 35 | let contract_addr = alice.as_deployer().deploy().await?.contract_address; 36 | let contract = PedersenExample::new(contract_addr, &alice.wallet); 37 | 38 | let PedersenExample::hashReturn { hash } = 39 | contract.hash([input_1, input_2]).call().await?; 40 | 41 | assert_eq!(hash, expected); 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /examples/poseidon/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poseidon-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-crypto.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | e2e.workspace = true 17 | tokio.workspace = true 18 | eyre.workspace = true 19 | 20 | [lib] 21 | crate-type = ["lib", "cdylib"] 22 | 23 | [features] 24 | e2e = [] 25 | export-abi = ["stylus-sdk/export-abi"] 26 | 27 | [[bin]] 28 | name = "poseidon-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/poseidon/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | extern crate alloc; 3 | 4 | use alloc::vec::Vec; 5 | 6 | use alloy_primitives::U256; 7 | use openzeppelin_crypto::{ 8 | arithmetic::{uint::Uint, BigInteger}, 9 | field::{instance::FpBN256, prime::PrimeField}, 10 | poseidon2::{instance::bn256::BN256Params, Poseidon2}, 11 | }; 12 | use stylus_sdk::prelude::*; 13 | 14 | #[entrypoint] 15 | #[storage] 16 | struct PoseidonExample; 17 | 18 | #[public] 19 | impl PoseidonExample { 20 | fn hash(&mut self, inputs: [U256; 2]) -> U256 { 21 | let mut hasher = Poseidon2::::new(); 22 | 23 | for input in inputs.iter() { 24 | let fp = FpBN256::from_bigint(Uint::from_bytes_le( 25 | &input.to_le_bytes_vec(), 26 | )); 27 | hasher.absorb(&fp); 28 | } 29 | 30 | let hash = hasher.squeeze(); 31 | let hash = hash.into_bigint().into_bytes_le(); 32 | 33 | U256::from_le_slice(&hash) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/poseidon/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | poseidon_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/poseidon/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract PoseidonExample { 7 | #[derive(Debug)] 8 | function hash(uint[2] memory inputs) external view returns (uint hash); 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /examples/poseidon/tests/poseidon.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "e2e")] 2 | 3 | use alloy_primitives::{hex, uint, U256}; 4 | use e2e::Account; 5 | use eyre::Result; 6 | 7 | use crate::abi::PoseidonExample; 8 | 9 | mod abi; 10 | 11 | // ============================================================================ 12 | // Integration Tests: Poseidon 13 | // ============================================================================ 14 | 15 | #[e2e::test] 16 | async fn poseidon_works(alice: Account) -> Result<()> { 17 | let contract_addr = alice.as_deployer().deploy().await?.contract_address; 18 | let contract = PoseidonExample::new(contract_addr, &alice.wallet); 19 | 20 | let PoseidonExample::hashReturn { hash } = 21 | contract.hash([uint!(123_U256), uint!(123456_U256)]).call().await?; 22 | 23 | let expected = U256::from_be_slice(&hex!( 24 | "16f70722695a5829a59319fbf746df957a513fdf72b070a67bb72db08070e5de" 25 | )); 26 | 27 | assert_eq!(hash, expected); 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /examples/safe-erc20/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "safe-erc20-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | eyre.workspace = true 17 | tokio.workspace = true 18 | e2e.workspace = true 19 | 20 | [features] 21 | e2e = [] 22 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 23 | 24 | [lib] 25 | crate-type = ["lib", "cdylib"] 26 | 27 | [[bin]] 28 | name = "safe-erc20-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/safe-erc20/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | extern crate alloc; 3 | 4 | use openzeppelin_stylus::token::erc20::utils::safe_erc20::{ 5 | self, ISafeErc20, SafeErc20, 6 | }; 7 | use stylus_sdk::{ 8 | alloy_primitives::{Address, U256}, 9 | prelude::*, 10 | }; 11 | 12 | #[entrypoint] 13 | #[storage] 14 | struct SafeErc20Example { 15 | safe_erc20: SafeErc20, 16 | } 17 | 18 | #[public] 19 | #[implements(ISafeErc20)] 20 | impl SafeErc20Example {} 21 | 22 | #[public] 23 | impl ISafeErc20 for SafeErc20Example { 24 | type Error = safe_erc20::Error; 25 | 26 | fn safe_transfer( 27 | &mut self, 28 | token: Address, 29 | to: Address, 30 | value: U256, 31 | ) -> Result<(), Self::Error> { 32 | self.safe_erc20.safe_transfer(token, to, value) 33 | } 34 | 35 | fn safe_transfer_from( 36 | &mut self, 37 | token: Address, 38 | from: Address, 39 | to: Address, 40 | value: U256, 41 | ) -> Result<(), Self::Error> { 42 | self.safe_erc20.safe_transfer_from(token, from, to, value) 43 | } 44 | 45 | fn safe_increase_allowance( 46 | &mut self, 47 | token: Address, 48 | spender: Address, 49 | value: U256, 50 | ) -> Result<(), Self::Error> { 51 | self.safe_erc20.safe_increase_allowance(token, spender, value) 52 | } 53 | 54 | fn safe_decrease_allowance( 55 | &mut self, 56 | token: Address, 57 | spender: Address, 58 | requested_decrease: U256, 59 | ) -> Result<(), Self::Error> { 60 | self.safe_erc20.safe_decrease_allowance( 61 | token, 62 | spender, 63 | requested_decrease, 64 | ) 65 | } 66 | 67 | fn force_approve( 68 | &mut self, 69 | token: Address, 70 | spender: Address, 71 | value: U256, 72 | ) -> Result<(), Self::Error> { 73 | self.safe_erc20.force_approve(token, spender, value) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/safe-erc20/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | safe_erc20_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/safe-erc20/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract SafeErc20 { 7 | function safeTransfer(address token, address to, uint256 value) external; 8 | function safeTransferFrom(address token, address from, address to, uint256 value) external; 9 | function safeIncreaseAllowance(address token, address spender, uint256 value) external; 10 | function safeDecreaseAllowance(address token, address spender, uint256 requestedDecrease) external; 11 | function forceApprove(address token, address spender, uint256 value) external; 12 | 13 | error SafeErc20FailedOperation(address token); 14 | error SafeErc20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); 15 | } 16 | 17 | contract Erc20 { 18 | error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); 19 | 20 | #[derive(Debug, PartialEq)] 21 | event Transfer(address indexed from, address indexed to, uint256 value); 22 | #[derive(Debug, PartialEq)] 23 | event Approval(address indexed owner, address indexed spender, uint256 value); 24 | } 25 | ); 26 | -------------------------------------------------------------------------------- /examples/safe-erc20/tests/mock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod erc20; 2 | pub mod erc20_force_approve; 3 | pub mod erc20_no_return; 4 | pub mod erc20_return_false; 5 | -------------------------------------------------------------------------------- /examples/vesting-wallet/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vesting-wallet-example" 3 | edition.workspace = true 4 | license.workspace = true 5 | repository.workspace = true 6 | publish = false 7 | version.workspace = true 8 | 9 | [dependencies] 10 | openzeppelin-stylus.workspace = true 11 | alloy-primitives.workspace = true 12 | stylus-sdk.workspace = true 13 | 14 | [dev-dependencies] 15 | alloy.workspace = true 16 | eyre.workspace = true 17 | tokio.workspace = true 18 | e2e.workspace = true 19 | 20 | [features] 21 | e2e = [] 22 | export-abi = ["stylus-sdk/export-abi", "openzeppelin-stylus/export-abi"] 23 | 24 | [lib] 25 | crate-type = ["lib", "cdylib"] 26 | 27 | [[bin]] 28 | name = "vesting-wallet-example" 29 | path = "src/main.rs" 30 | -------------------------------------------------------------------------------- /examples/vesting-wallet/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(any(test, feature = "export-abi")), no_main)] 2 | 3 | #[cfg(not(any(test, feature = "export-abi")))] 4 | #[no_mangle] 5 | pub extern "C" fn main() {} 6 | 7 | #[cfg(feature = "export-abi")] 8 | fn main() { 9 | vesting_wallet_example::print_from_args(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/vesting-wallet/tests/abi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use alloy::sol; 3 | 4 | sol!( 5 | #[sol(rpc)] 6 | contract VestingWallet { 7 | function owner() public view virtual returns (address owner); 8 | function start() external view returns (uint256 start); 9 | function duration() external view returns (uint256 duration); 10 | function end() external view returns (uint256 end); 11 | function released() external view returns (uint256 released); 12 | function released(address token) external view returns (uint256 released); 13 | function releasable() external view returns (uint256 releasable); 14 | function releasable(address token) external view returns (uint256 releasable); 15 | function release() external; 16 | function release(address token) external; 17 | function vestedAmount(uint64 timestamp) external view returns (uint256 vestedAmount); 18 | function vestedAmount(address token, uint64 timestamp) external view returns (uint256 vestedAmount); 19 | 20 | error OwnableUnauthorizedAccount(address account); 21 | error OwnableInvalidOwner(address owner); 22 | error ReleaseEtherFailed(); 23 | error SafeErc20FailedOperation(address token); 24 | error InvalidToken(address token); 25 | 26 | #[derive(Debug, PartialEq)] 27 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 28 | #[derive(Debug, PartialEq)] 29 | event EtherReleased(uint256 amount); 30 | #[derive(Debug, PartialEq)] 31 | event ERC20Released(address indexed token, uint256 amount); 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /examples/vesting-wallet/tests/mock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod erc20; 2 | pub mod erc20_return_false; 3 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys = "0.4.9" 12 | stylus-sdk = "=0.9.0" 13 | alloy-primitives = { version = "=0.8.20", default-features = false } 14 | 15 | [dependencies.openzeppelin-crypto] 16 | path = "../lib/crypto" 17 | 18 | # fuzz targets 19 | 20 | [[bin]] 21 | name = "merkle" 22 | path = "fuzz_targets/merkle.rs" 23 | test = false 24 | doc = false 25 | bench = false 26 | 27 | [[bin]] 28 | name = "keccak" 29 | path = "fuzz_targets/keccak.rs" 30 | test = false 31 | doc = false 32 | bench = false 33 | 34 | # corpus generators 35 | 36 | # name must be in format 'gen-{{fuzz-target}}-corpus 37 | [[bin]] 38 | name = "gen-merkle-corpus" 39 | path = "src/merkle/corpus.rs" 40 | test = false 41 | doc = false 42 | bench = false 43 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/keccak.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use openzeppelin_crypto::{ 4 | hash::{BuildHasher, Hasher}, 5 | keccak::KeccakBuilder, 6 | }; 7 | 8 | fuzz_target!(|data: &[u8]| { 9 | let mut hasher = KeccakBuilder.build_hasher(); 10 | hasher.update(data); 11 | _ = hasher.finalize(); 12 | }); 13 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/merkle.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use openzeppelin_crypto::merkle::Verifier; 5 | use test_fuzz::Input; 6 | 7 | fuzz_target!(|input: Input| { 8 | let Input { root, leaves, proof, proof_flags } = input; 9 | 10 | let multi_verif = 11 | Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); 12 | 13 | // If we have a single leaf, also test the regular verification 14 | if leaves.len() == 1 { 15 | let single_verif = Verifier::verify(&proof, root, leaves[0]); 16 | 17 | // ensure the results match if no errors occurred 18 | if let Ok(multi_verif) = multi_verif { 19 | assert_eq!(single_verif, multi_verif); 20 | } 21 | 22 | // the reason we don't make any assumptions in case of multi-proof 23 | // errors is that it is possible that fuzzer generates invalid 24 | // proof_flags for valid merkle tree, returning an error 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /fuzz/src/lib.rs: -------------------------------------------------------------------------------- 1 | use libfuzzer_sys::arbitrary::{ 2 | Arbitrary, Result as ArbitraryResult, Unstructured, 3 | }; 4 | 5 | type Bytes32 = [u8; 32]; 6 | 7 | const MAX_LEAVES: usize = 64; 8 | 9 | #[derive(Debug)] 10 | pub struct Input { 11 | pub root: Bytes32, 12 | pub leaves: Vec, 13 | pub proof: Vec, 14 | pub proof_flags: Vec, 15 | } 16 | 17 | impl<'a> Arbitrary<'a> for Input { 18 | fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult { 19 | let root = u.arbitrary()?; 20 | let proof: Vec = u.arbitrary()?; 21 | 22 | // leaves.len() + proof.len() >= 1 23 | let min_leaves = if proof.is_empty() { 1 } else { 0 }; 24 | // ensure we don't go overboard with number of leaves 25 | let num_leaves = u.int_in_range(min_leaves..=MAX_LEAVES)?; 26 | let mut leaves = Vec::with_capacity(num_leaves); 27 | for _ in 0..num_leaves { 28 | leaves.push(u.arbitrary()?); 29 | } 30 | 31 | // ensure we pass the proof flag length check 32 | let proof_flag_len = leaves.len() + proof.len() - 1; 33 | let mut proof_flags = Vec::with_capacity(proof_flag_len); 34 | for _ in 0..proof_flag_len { 35 | proof_flags.push(u.arbitrary()?); 36 | } 37 | 38 | Ok(Input { root, leaves, proof, proof_flags }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/crypto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openzeppelin-crypto" 3 | description = "Cryptographic Utilities" 4 | edition.workspace = true 5 | categories = ["cryptography", "algorithms", "no-std", "wasm"] 6 | keywords = ["crypto", "web3", "blockchain", "merkle"] 7 | license.workspace = true 8 | repository.workspace = true 9 | version.workspace = true 10 | 11 | [dependencies] 12 | tiny-keccak.workspace = true 13 | num-traits.workspace = true 14 | zeroize.workspace = true 15 | educe.workspace = true 16 | hex-literal.workspace = true 17 | 18 | [dev-dependencies] 19 | alloy-primitives = { workspace = true, features = ["arbitrary"] } 20 | rand.workspace = true 21 | proptest.workspace = true 22 | 23 | [features] 24 | 25 | [lints] 26 | workspace = true 27 | -------------------------------------------------------------------------------- /lib/crypto/README.md: -------------------------------------------------------------------------------- 1 | # Cryptographic Utilities 2 | 3 | Common cryptographic procedures for a blockchain environment. 4 | 5 | > [!WARNING] 6 | > Note that `crypto` is still `0.*.*`, so breaking changes 7 | > [may occur at any time](https://semver.org/#spec-item-4). If you must depend 8 | > on `crypto`, we recommend pinning to a specific version, i.e., `=0.y.z`. 9 | 10 | ## Verifying Merkle Proofs 11 | 12 | [`merkle.rs`](./src/merkle.rs) provides: 13 | 14 | - A `verify` function which can prove that some value is part of a 15 | [Merkle tree]. 16 | - A `verify_multi_proof` function which can prove multiple values are part of a 17 | [Merkle tree]. 18 | 19 | [Merkle tree]: https://en.wikipedia.org/wiki/Merkle_tree 20 | 21 | ## Feature Flags 22 | 23 | This crate exposes its modules behind feature gates to ensure the bare minimum 24 | is included in consumer codebases. You can check the current feature flags in 25 | the [Cargo.toml](./Cargo.toml) file. 26 | 27 | ## Security 28 | 29 | Refer to our [Security Policy](../../SECURITY.md) for more details. 30 | -------------------------------------------------------------------------------- /lib/crypto/proptest-regressions/field/fp.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | -------------------------------------------------------------------------------- /lib/crypto/src/const_helpers.rs: -------------------------------------------------------------------------------- 1 | //! This module contains helpers for functions with constant context, like 2 | //! [`ct_for`] - constant time `for` cycle, as well as its optimized versions 3 | //! like [`ct_for_unroll6`], that performs [loop unroll] optimization and can be 4 | //! used both from compile time and runtime. 5 | //! 6 | //! Beware of using an optimized version everywhere, since it can bloat 7 | //! binary (WASM) size easily. 8 | //! Measure impact first. 9 | //! 10 | //! [loop unroll]: https://en.wikipedia.org/wiki/Loop_unrolling 11 | 12 | /// Allows writing `for` cycle in constant context. 13 | #[macro_export] 14 | macro_rules! ct_for { 15 | (($i:ident in $start:tt.. $end:tt) $code:expr) => {{ 16 | let mut $i = $start; 17 | loop { 18 | $crate::cycle!($i, $end, $code); 19 | } 20 | }}; 21 | } 22 | 23 | /// Allows writing `for` cycle in constant context, with 2 stages loop unroll 24 | /// optimization. 25 | #[macro_export] 26 | macro_rules! ct_for_unroll2 { 27 | (($i:ident in $start:tt.. $end:tt) $code:expr) => {{ 28 | let mut $i = $start; 29 | loop { 30 | $crate::cycle!($i, $end, $code); 31 | $crate::cycle!($i, $end, $code); 32 | } 33 | }}; 34 | } 35 | 36 | /// Allows writing `for` cycle in constant context, with 4 stages loop unroll 37 | /// optimization. 38 | #[macro_export] 39 | macro_rules! ct_for_unroll4 { 40 | (($i:ident in $start:tt.. $end:tt) $code:expr) => {{ 41 | let mut $i = $start; 42 | loop { 43 | $crate::cycle!($i, $end, $code); 44 | $crate::cycle!($i, $end, $code); 45 | $crate::cycle!($i, $end, $code); 46 | $crate::cycle!($i, $end, $code); 47 | } 48 | }}; 49 | } 50 | 51 | /// Allows writing `for` cycle in constant context, with 6 stages loop unroll 52 | /// optimization. 53 | #[macro_export] 54 | macro_rules! ct_for_unroll6 { 55 | (($i:ident in $start:tt.. $end:tt) $code:expr) => {{ 56 | let mut $i = $start; 57 | loop { 58 | $crate::cycle!($i, $end, $code); 59 | $crate::cycle!($i, $end, $code); 60 | $crate::cycle!($i, $end, $code); 61 | $crate::cycle!($i, $end, $code); 62 | $crate::cycle!($i, $end, $code); 63 | $crate::cycle!($i, $end, $code); 64 | } 65 | }}; 66 | } 67 | 68 | /// Allows writing `for` cycle in constant context, with 8 stages loop unroll 69 | /// optimization. 70 | #[macro_export] 71 | macro_rules! ct_for_unroll8 { 72 | (($i:ident in $start:tt.. $end:tt) $code:expr) => {{ 73 | let mut $i = $start; 74 | loop { 75 | $crate::cycle!($i, $end, $code); 76 | $crate::cycle!($i, $end, $code); 77 | $crate::cycle!($i, $end, $code); 78 | $crate::cycle!($i, $end, $code); 79 | $crate::cycle!($i, $end, $code); 80 | $crate::cycle!($i, $end, $code); 81 | $crate::cycle!($i, $end, $code); 82 | $crate::cycle!($i, $end, $code); 83 | } 84 | }}; 85 | } 86 | 87 | /// Single cycle step in the loop. 88 | #[macro_export] 89 | macro_rules! cycle { 90 | ($i:ident, $end:tt, $code:expr) => {{ 91 | if $i < $end { 92 | $code 93 | } else { 94 | break; 95 | } 96 | $i += 1; 97 | }}; 98 | } 99 | -------------------------------------------------------------------------------- /lib/crypto/src/field/group.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a generic interface for groups with additive notation. 2 | 3 | use core::{ 4 | fmt::{Debug, Display}, 5 | hash::Hash, 6 | iter::Sum, 7 | ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, 8 | }; 9 | 10 | use num_traits::Zero; 11 | use zeroize::Zeroize; 12 | 13 | use crate::field::Field; 14 | 15 | /// Defines an abstract group with additive notation. 16 | /// Support addition and subtraction with itself and multiplication by scalar. 17 | /// Scalar and group can be different types. 18 | /// 19 | /// E.g., points on an elliptic curve define an additive group and can be 20 | /// multiplied by a scalar. 21 | pub trait AdditiveGroup: 22 | Eq 23 | + 'static 24 | + Sized 25 | + Copy 26 | + Clone 27 | + Default 28 | + Send 29 | + Sync 30 | + Hash 31 | + Debug 32 | + Display 33 | + Zeroize 34 | + Zero 35 | + Neg 36 | + Add 37 | + Sub 38 | + Mul<::Scalar, Output = Self> 39 | + AddAssign 40 | + SubAssign 41 | + MulAssign<::Scalar> 42 | + for<'a> Add<&'a Self, Output = Self> 43 | + for<'a> Sub<&'a Self, Output = Self> 44 | + for<'a> Mul<&'a ::Scalar, Output = Self> 45 | + for<'a> AddAssign<&'a Self> 46 | + for<'a> SubAssign<&'a Self> 47 | + for<'a> MulAssign<&'a ::Scalar> 48 | + for<'a> Add<&'a mut Self, Output = Self> 49 | + for<'a> Sub<&'a mut Self, Output = Self> 50 | + for<'a> Mul<&'a mut ::Scalar, Output = Self> 51 | + for<'a> AddAssign<&'a mut Self> 52 | + for<'a> SubAssign<&'a mut Self> 53 | + for<'a> MulAssign<&'a mut ::Scalar> 54 | + Sum 55 | + for<'a> Sum<&'a Self> 56 | { 57 | /// Scalar associated with the group. 58 | type Scalar: Field; 59 | 60 | /// Additive identity of the group. 61 | const ZERO: Self; 62 | 63 | /// Doubles `self`. 64 | #[must_use] 65 | fn double(&self) -> Self { 66 | let mut copy = *self; 67 | copy.double_in_place(); 68 | copy 69 | } 70 | 71 | /// Doubles `self` in place. 72 | fn double_in_place(&mut self) -> &mut Self { 73 | self.add_assign(*self); 74 | self 75 | } 76 | 77 | /// Negates `self` in place. 78 | fn neg_in_place(&mut self) -> &mut Self { 79 | *self = -(*self); 80 | self 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/crypto/src/field/instance.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the field instances for some popular curves. 2 | 3 | #![allow(missing_docs)] 4 | 5 | use crate::{ 6 | arithmetic::uint::{U256, U64}, 7 | field::fp::{Fp256, Fp64, FpParams, LIMBS_256, LIMBS_64}, 8 | fp_from_num, from_num, 9 | }; 10 | 11 | pub type FpVesta = Fp256; 12 | pub struct VestaParam; 13 | impl FpParams for VestaParam { 14 | const GENERATOR: Fp256 = fp_from_num!("5"); 15 | const MODULUS: U256 = from_num!("28948022309329048855892746252171976963363056481941647379679742748393362948097"); 16 | } 17 | 18 | pub type FpBabyBear = Fp64; 19 | pub struct BabyBearParam; 20 | impl FpParams for BabyBearParam { 21 | const GENERATOR: Fp64 = fp_from_num!("31"); 22 | const MODULUS: U64 = from_num!("2013265921"); 23 | } 24 | 25 | pub type FpBLS12 = Fp256; 26 | pub struct BLS12Param; 27 | impl FpParams for BLS12Param { 28 | const GENERATOR: Fp256 = fp_from_num!("7"); 29 | const MODULUS: U256 = from_num!("52435875175126190479447740508185965837690552500527637822603658699938581184513"); 30 | } 31 | 32 | pub type FpBN256 = Fp256; 33 | pub struct BN256Param; 34 | impl FpParams for BN256Param { 35 | const GENERATOR: Fp256 = fp_from_num!("7"); 36 | const MODULUS: U256 = from_num!("21888242871839275222246405745257275088548364400416034343698204186575808495617"); 37 | } 38 | 39 | pub type FpGoldiLocks = Fp64; 40 | pub struct GoldiLocksParam; 41 | impl FpParams for GoldiLocksParam { 42 | const GENERATOR: Fp64 = fp_from_num!("7"); 43 | const MODULUS: U64 = from_num!("18446744069414584321"); 44 | } 45 | 46 | pub type FpPallas = Fp256; 47 | pub struct PallasParam; 48 | impl FpParams for PallasParam { 49 | const GENERATOR: Fp256 = fp_from_num!("5"); 50 | const MODULUS: U256 = from_num!("28948022309329048855892746252171976963363056481941560715954676764349967630337"); 51 | } 52 | -------------------------------------------------------------------------------- /lib/crypto/src/field/prime.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a generic interface for finite prime fields. 2 | 3 | use crate::{arithmetic::BigInteger, field::Field}; 4 | 5 | /// Defines an abstract prime field. 6 | /// I.e., the field of integers of prime module [`Self::MODULUS`]. 7 | pub trait PrimeField: 8 | Field + From<::BigInt> + Into<::BigInt> 9 | { 10 | /// A `BigInteger` type that can represent elements of this field. 11 | type BigInt: BigInteger; 12 | 13 | /// The modulus `p`. 14 | const MODULUS: Self::BigInt; 15 | 16 | /// The size of the modulus in bits. 17 | const MODULUS_BIT_SIZE: usize; 18 | 19 | /// Returns the characteristic of the field, 20 | /// in little-endian representation. 21 | #[must_use] 22 | fn characteristic() -> Self::BigInt { 23 | Self::MODULUS 24 | } 25 | 26 | /// Construct a prime field element from a big integer. 27 | fn from_bigint(repr: Self::BigInt) -> Self; 28 | 29 | /// Converts an element of the prime field into an integer less than 30 | /// [`Self::MODULUS`]. 31 | fn into_bigint(self) -> Self::BigInt; 32 | } 33 | -------------------------------------------------------------------------------- /lib/crypto/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Common cryptographic procedures for a blockchain environment. 3 | 4 | > Note that `crypto` is still `0.*.*`, so breaking changes 5 | > [may occur at any time](https://semver.org/#spec-item-4). If you must depend 6 | > on `crypto`, we recommend pinning to a specific version, i.e., `=0.y.z`. 7 | 8 | ## Verifying Merkle Proofs 9 | 10 | [`merkle.rs`](./src/merkle.rs) provides: 11 | 12 | - A `verify` function which can prove that some value is part of a 13 | [Merkle tree]. 14 | - A `verify_multi_proof` function which can prove multiple values are part of a 15 | [Merkle tree]. 16 | 17 | [Merkle tree]: https://en.wikipedia.org/wiki/Merkle_tree 18 | 19 | */ 20 | 21 | #![allow(clippy::module_name_repetitions)] 22 | #![allow(clippy::inline_always)] 23 | #![allow(clippy::unreadable_literal)] 24 | #![allow(clippy::many_single_char_names)] 25 | #![cfg_attr(not(test), no_std, no_main)] 26 | extern crate alloc; 27 | extern crate core; 28 | 29 | pub mod arithmetic; 30 | pub mod bits; 31 | #[macro_use] 32 | pub mod field; 33 | mod const_helpers; 34 | pub mod hash; 35 | pub mod keccak; 36 | pub mod merkle; 37 | pub mod pedersen; 38 | pub mod poseidon2; 39 | 40 | pub use keccak::KeccakBuilder; 41 | 42 | pub mod curve; 43 | #[cfg(test)] 44 | mod test_helpers; 45 | -------------------------------------------------------------------------------- /lib/crypto/src/pedersen/instance/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains Pedersen Hash instances for some popular curves. 2 | pub mod starknet; 3 | -------------------------------------------------------------------------------- /lib/crypto/src/pedersen/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains Pedersen Hash Function implementation. 2 | /// 3 | /// Based on the [Starknet] implementation of the Pedersen Hash Function. 4 | /// 5 | /// [Starknet]: 6 | pub mod instance; 7 | pub mod params; 8 | 9 | use crate::{ 10 | curve::{ 11 | sw::{Affine, Projective, SWCurveConfig}, 12 | AffineRepr, CurveConfig, PrimeGroup, 13 | }, 14 | field::prime::PrimeField, 15 | pedersen::params::PedersenParams, 16 | }; 17 | 18 | /// Pedersen hash. 19 | #[derive(Clone, Debug)] 20 | pub struct Pedersen, P: SWCurveConfig> 21 | where 22 |

::BaseField: PrimeField, 23 | { 24 | params: core::marker::PhantomData, 25 | curve: core::marker::PhantomData

, 26 | } 27 | 28 | impl, P: SWCurveConfig> Default for Pedersen 29 | where 30 |

::BaseField: PrimeField, 31 | { 32 | fn default() -> Self { 33 | Self::new() 34 | } 35 | } 36 | 37 | impl, P: SWCurveConfig> Pedersen 38 | where 39 |

::BaseField: PrimeField, 40 | { 41 | #[must_use] 42 | #[inline] 43 | /// Creates a new Pedersen hash instance. 44 | pub fn new() -> Self { 45 | Self { 46 | params: core::marker::PhantomData, 47 | curve: core::marker::PhantomData, 48 | } 49 | } 50 | 51 | fn process_single_element( 52 | element: P::BaseField, 53 | p1: Projective

, 54 | p2: Projective

, 55 | ) -> Projective

{ 56 | let element = element.into_bigint(); 57 | 58 | let high_nibble = element >> F::LOW_PART_BITS; 59 | let low_part = element & F::LOW_PART_MASK; 60 | 61 | p1.mul_bigint(low_part) + p2.mul_bigint(high_nibble) 62 | } 63 | 64 | /// Computes the Starkware version of the Pedersen hash of x and y. 65 | /// 66 | /// The hash is defined by: 67 | /// [`PedersenParams::P_0`] + `x_low` * [`PedersenParams::P_1`] + 68 | /// `x_high` * [`PedersenParams::P_2`] + `y_low` * [`PedersenParams::P_3`] + 69 | /// `y_high` * [`PedersenParams::P_4`] 70 | /// 71 | /// where `x_low` is the 248 low bits of `x`, `x_high` is the 4 high bits of 72 | /// `x` and similarly for `y`. [`PedersenParams::P_0`], 73 | /// [`PedersenParams::P_1`], [`PedersenParams::P_2`], 74 | /// [`PedersenParams::P_3`], [`PedersenParams::P_4`] are constant points 75 | /// generated from the digits of pi. 76 | /// 77 | /// # Arguments 78 | /// 79 | /// * `&self` - Pedersen hasher instance. 80 | /// * `x` - The x coordinate of the point to hash. 81 | /// * `y` - The y coordinate of the point to hash. 82 | #[must_use] 83 | pub fn hash( 84 | &self, 85 | x: P::BaseField, 86 | y: P::BaseField, 87 | ) -> Option { 88 | let hash: Projective

= F::P_0 89 | + Self::process_single_element(x, F::P_1.into(), F::P_2.into()) 90 | + Self::process_single_element(y, F::P_3.into(), F::P_4.into()); 91 | 92 | let hash: Affine

= hash.into(); 93 | hash.x() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/crypto/src/pedersen/params.rs: -------------------------------------------------------------------------------- 1 | //! Pedersen hash parameters. 2 | 3 | use crate::{ 4 | curve::{ 5 | sw::{Affine, SWCurveConfig}, 6 | CurveConfig, 7 | }, 8 | field::prime::PrimeField, 9 | }; 10 | 11 | /// Pedersen hash parameters. 12 | pub trait PedersenParams 13 | where 14 |

::BaseField: PrimeField, 15 | { 16 | /// Number of elements in the hash. 17 | const N_ELEMENT_BITS_HASH: usize; 18 | 19 | /// Shift point. 20 | const P_0: Affine

; 21 | 22 | /// Constant point -- `P_1`. 23 | const P_1: Affine

; 24 | /// Constant point -- `P_2`. 25 | const P_2: Affine

; 26 | /// Constant point -- `P_3`. 27 | const P_3: Affine

; 28 | /// Constant point -- `P_4`. 29 | const P_4: Affine

; 30 | 31 | /// Low bits of a value to hash. 32 | const LOW_PART_BITS: u32; 33 | /// Low part mask for a value to hash. 34 | const LOW_PART_MASK: ::BigInt; 35 | } 36 | -------------------------------------------------------------------------------- /lib/crypto/src/poseidon2/instance/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the poseidon hash instances for some popular finite 2 | //! field instances. 3 | pub mod babybear; 4 | pub mod bls12; 5 | pub mod bn256; 6 | pub mod goldilocks; 7 | pub mod pallas; 8 | pub mod vesta; 9 | -------------------------------------------------------------------------------- /lib/crypto/src/poseidon2/params.rs: -------------------------------------------------------------------------------- 1 | //! This module contains a trait with poseidon hash parameters. 2 | //! 3 | //! Consumer of this trait should implement the parameters for the specific 4 | //! poseidon hash instance. 5 | //! Or use the existing instances in the [`crate::poseidon2::instance`] module. 6 | 7 | use crate::field::prime::PrimeField; 8 | 9 | /// Poseidon hash parameters. 10 | pub trait PoseidonParams { 11 | /// State size. 12 | const T: usize; 13 | 14 | /// Sbox degree. 15 | const D: u8; 16 | 17 | /// Capacity of the sponge construction. 18 | /// Determines the number of elements not affected directly by input 19 | /// or not reflected in the output of the sponge hash function. 20 | const CAPACITY: usize; 21 | 22 | /// Number of full rounds. 23 | const ROUNDS_F: usize; 24 | 25 | /// Number of partial rounds. 26 | const ROUNDS_P: usize; 27 | 28 | /// MDS (Maximum Distance Separable) matrix used in the Poseidon 29 | /// permutation. 30 | const MAT_INTERNAL_DIAG_M_1: &'static [F]; 31 | 32 | /// The round constants used in the full and partial rounds of the Poseidon 33 | /// permutation. 34 | const ROUND_CONSTANTS: &'static [&'static [F]]; 35 | } 36 | -------------------------------------------------------------------------------- /lib/crypto/src/test_helpers.rs: -------------------------------------------------------------------------------- 1 | use proptest::prelude::*; 2 | 3 | /// Creates a proptest strategy for non-empty vectors of random bytes. 4 | /// 5 | /// Maximum vector size is determined by proptest's default configuration. 6 | pub(crate) fn non_empty_u8_vec_strategy() -> impl Strategy> { 7 | prop::collection::vec( 8 | any::(), 9 | 1..ProptestConfig::default().max_default_size_range, 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /lib/e2e-proc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "e2e-proc" 3 | description = "End-to-end Testing Procedural Macros" 4 | version = "0.2.0" 5 | categories = ["development-tools::testing", "cryptography::cryptocurrencies"] 6 | keywords = ["arbitrum", "ethereum", "stylus", "integration-testing", "tests"] 7 | authors.workspace = true 8 | edition.workspace = true 9 | license.workspace = true 10 | repository.workspace = true 11 | 12 | [dependencies] 13 | proc-macro2.workspace = true 14 | quote.workspace = true 15 | syn.workspace = true 16 | 17 | [lib] 18 | proc-macro = true 19 | 20 | [lints] 21 | workspace = true 22 | -------------------------------------------------------------------------------- /lib/e2e-proc/README.md: -------------------------------------------------------------------------------- 1 | # End-to-end Procedural Macros 2 | 3 | This crate contains procedural macros used in [e2e]. 4 | 5 | [e2e]: ../e2e/README.md 6 | -------------------------------------------------------------------------------- /lib/e2e-proc/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | use proc_macro::TokenStream; 3 | 4 | mod test; 5 | 6 | /// Defines an end-to-end Stylus contract test that sets up `e2e::Account`s 7 | /// based on the function's parameters. 8 | /// 9 | /// # Examples 10 | /// 11 | /// ```rust,ignore 12 | /// #[e2e::test] 13 | /// async fn foo(alice: Account, bob: Account) -> eyre::Result<()> { 14 | /// let charlie = Account::new().await?; 15 | /// // ... 16 | /// } 17 | /// ``` 18 | #[proc_macro_attribute] 19 | pub fn test(attr: TokenStream, input: TokenStream) -> TokenStream { 20 | test::test(&attr, input) 21 | } 22 | -------------------------------------------------------------------------------- /lib/e2e-proc/src/test.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::{parse_macro_input, FnArg}; 4 | 5 | /// Shorthand to print nice errors. 6 | macro_rules! error { 7 | ($tokens:expr, $($msg:expr),+ $(,)?) => {{ 8 | let error = syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+)); 9 | return error.to_compile_error().into(); 10 | }}; 11 | (@ $tokens:expr, $($msg:expr),+ $(,)?) => {{ 12 | return Err(syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+))) 13 | }}; 14 | } 15 | 16 | /// Defines an end-to-end test that injects test accounts through parameters. 17 | /// 18 | /// For more information see [`crate::test`]. 19 | pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { 20 | let item_fn = parse_macro_input!(input as syn::ItemFn); 21 | let attrs = &item_fn.attrs; 22 | let sig = &item_fn.sig; 23 | let fn_name = &sig.ident; 24 | let fn_return_type = &sig.output; 25 | let fn_stmts = &item_fn.block.stmts; 26 | let fn_args = &sig.inputs; 27 | 28 | let account_declarations = fn_args.into_iter().map(|arg| { 29 | let FnArg::Typed(arg) = arg else { 30 | error!(arg, "unexpected receiver argument in test signature"); 31 | }; 32 | let account_arg_binding = &arg.pat; 33 | let account_ty = &arg.ty; 34 | quote! { 35 | let #account_arg_binding = #account_ty::new().await?; 36 | } 37 | }); 38 | quote! { 39 | #( #attrs )* 40 | #[tokio::test] 41 | async fn #fn_name() #fn_return_type { 42 | #( #account_declarations )* 43 | #( #fn_stmts )* 44 | } 45 | } 46 | .into() 47 | } 48 | -------------------------------------------------------------------------------- /lib/e2e/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "e2e" 3 | description = "End-to-end Testing for Stylus" 4 | version = "0.3.1" 5 | categories = ["development-tools::testing", "cryptography::cryptocurrencies"] 6 | keywords = ["arbitrum", "ethereum", "stylus", "integration-testing", "tests"] 7 | authors.workspace = true 8 | edition.workspace = true 9 | license.workspace = true 10 | repository.workspace = true 11 | 12 | [dependencies] 13 | alloy.workspace = true 14 | tokio = { workspace = true, features = ["process"] } 15 | eyre.workspace = true 16 | regex.workspace = true 17 | once_cell.workspace = true 18 | e2e-proc.workspace = true 19 | stylus-sdk.workspace = true 20 | toml = "0.8.13" 21 | 22 | [lints] 23 | workspace = true 24 | -------------------------------------------------------------------------------- /lib/e2e/src/constructor_macro.rs: -------------------------------------------------------------------------------- 1 | /// Constructor data. 2 | pub struct Constructor { 3 | /// Constructor signature. 4 | pub signature: String, 5 | /// Constructor arguments. 6 | pub args: Vec, 7 | } 8 | 9 | /// Generates a function selector for the given method and its args. 10 | #[macro_export] 11 | macro_rules! constructor { 12 | () => {{ 13 | $crate::Constructor { 14 | signature: "constructor()".to_string(), 15 | args: vec![], 16 | } 17 | }}; 18 | 19 | ($first:expr $(, $rest:expr)* $(,)?) => {{ 20 | fn get_abi_str(_: T) -> &'static str { 21 | ::ABI.as_str() 22 | } 23 | 24 | let signature_params = { 25 | let mut params = vec![get_abi_str($first)]; 26 | $(params.push(get_abi_str($rest));)* 27 | params.join(",") 28 | }; 29 | 30 | let args = vec![$first.to_string()$(, $rest.to_string())*]; 31 | 32 | $crate::Constructor { 33 | signature: format!("constructor({})", signature_params), 34 | args, 35 | } 36 | }}; 37 | } 38 | -------------------------------------------------------------------------------- /lib/e2e/src/environment.rs: -------------------------------------------------------------------------------- 1 | use std::{path::PathBuf, process::Command}; 2 | 3 | use eyre::Context; 4 | 5 | /// Gets expected path to the nitro test node. 6 | pub(crate) fn get_node_path() -> eyre::Result { 7 | let manifest_dir = get_workspace_root()?; 8 | Ok(manifest_dir.join("nitro-testnode")) 9 | } 10 | 11 | /// Runs the following command to get the worskpace root: 12 | /// 13 | /// ```bash 14 | /// git rev-parse --show-toplevel 15 | /// ``` 16 | pub(crate) fn get_workspace_root() -> eyre::Result { 17 | let output = Command::new("git") 18 | .arg("rev-parse") 19 | .arg("--show-toplevel") 20 | .output() 21 | .wrap_err("should run `git rev-parse --show-toplevel`")?; 22 | 23 | let path = String::from_utf8_lossy(&output.stdout) 24 | .trim() 25 | .to_string() 26 | .parse::() 27 | .wrap_err("failed to parse manifest dir path")?; 28 | Ok(path) 29 | } 30 | -------------------------------------------------------------------------------- /lib/e2e/src/event.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use alloy::{rpc::types::eth::TransactionReceipt, sol_types::SolEvent}; 4 | 5 | use crate::Receipt; 6 | 7 | /// Extension trait for asserting an event gets emitted. 8 | pub trait Ext { 9 | /// Asserts the contract emitted the `expected` event. 10 | fn emits(&self, expected: E) -> bool; 11 | } 12 | 13 | impl Ext for TransactionReceipt 14 | where 15 | E: SolEvent, 16 | E: PartialEq, 17 | { 18 | fn emits(&self, expected: E) -> bool { 19 | // Extract all events that are the expected type. 20 | self.inner 21 | .logs() 22 | .iter() 23 | .filter_map(|log| log.log_decode().ok()) 24 | .map(|log| log.inner.data) 25 | .any(|event| expected == event) 26 | } 27 | } 28 | 29 | impl Ext for Receipt 30 | where 31 | E: SolEvent, 32 | E: PartialEq, 33 | { 34 | fn emits(&self, expected: E) -> bool { 35 | self.inner.emits(expected) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/e2e/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | mod account; 3 | mod constructor_macro; 4 | mod deploy; 5 | mod environment; 6 | mod error; 7 | mod event; 8 | mod project; 9 | mod receipt; 10 | mod system; 11 | 12 | pub use account::Account; 13 | pub use constructor_macro::Constructor; 14 | pub use deploy::{ContractDeploymentError, ContractInitializationError}; 15 | pub use e2e_proc::test; 16 | pub use error::{Panic, PanicCode, Revert}; 17 | pub use event::Ext as EventExt; 18 | pub use receipt::Receipt; 19 | pub use system::{fund_account, Wallet, DEPLOYER_ADDRESS}; 20 | 21 | /// This macro provides a shorthand for broadcasting the transaction to the 22 | /// network. 23 | /// 24 | /// See: 25 | /// 26 | /// # Examples 27 | /// 28 | /// ```rust,ignore 29 | /// #[e2e::test] 30 | /// async fn foo(alice: Account) -> eyre::Result<()> { 31 | /// let contract_addr = alice.as_deployer().deploy().await?.contract_address; 32 | /// let contract = Erc721::new(contract_addr, &alice.wallet); 33 | /// 34 | /// let alice_addr = alice.address(); 35 | /// let token_id = random_token_id(); 36 | /// let pending_tx = send!(contract.mint(alice_addr, token_id))?; 37 | /// // ... 38 | /// } 39 | #[macro_export] 40 | macro_rules! send { 41 | ($e:expr) => { 42 | $e.send().await 43 | }; 44 | } 45 | 46 | /// This macro provides a shorthand for broadcasting the transaction 47 | /// to the network, and then waiting for the given number of confirmations. 48 | /// 49 | /// See: 50 | /// 51 | /// # Examples 52 | /// 53 | /// ```rust,ignore 54 | /// #[e2e::test] 55 | /// async fn foo(alice: Account) -> eyre::Result<()> { 56 | /// let contract_addr = alice.as_deployer().deploy().await?.contract_address; 57 | /// let contract = Erc721::new(contract_addr, &alice.wallet); 58 | /// 59 | /// let alice_addr = alice.address(); 60 | /// let token_id = random_token_id(); 61 | /// let result = watch!(contract.mint(alice_addr, token_id))?; 62 | /// // ... 63 | /// } 64 | #[macro_export] 65 | macro_rules! watch { 66 | ($e:expr) => { 67 | $crate::send!($e)?.watch().await 68 | }; 69 | } 70 | 71 | /// This macro provides a shorthand for broadcasting the transaction 72 | /// to the network, waiting for the given number of confirmations, and then 73 | /// fetching the transaction receipt. 74 | /// 75 | /// See: 76 | /// 77 | /// # Examples 78 | /// 79 | /// ```rust,ignore 80 | /// #[e2e::test] 81 | /// async fn foo(alice: Account) -> eyre::Result<()> { 82 | /// let contract_addr = alice.as_deployer().deploy().await?.contract_address; 83 | /// let contract = Erc721::new(contract_addr, &alice.wallet); 84 | /// 85 | /// let alice_addr = alice.address(); 86 | /// let token_id = random_token_id(); 87 | /// let receipt = receipt!(contract.mint(alice_addr, token_id))?; 88 | /// // ... 89 | /// } 90 | #[macro_export] 91 | macro_rules! receipt { 92 | ($e:expr) => { 93 | $crate::send!($e)?.get_receipt().await 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /lib/e2e/src/receipt.rs: -------------------------------------------------------------------------------- 1 | use alloy::{primitives::Address, rpc::types::TransactionReceipt}; 2 | 3 | /// Transaction receipt wrapper that contains both the receipt of the 4 | /// transaction sent to the StylusDeployer, and the contract address of the 5 | /// created/activated or even would-be created contract. 6 | /// 7 | /// This is necessary because calling [`TransactionReceipt::contract_address`] 8 | /// would return the address of StylusDeployer, instead of the newly deployed 9 | /// contract. 10 | #[derive(Debug)] 11 | pub struct Receipt { 12 | /// Transaction receipt of the tx sent to StylusDeployer. 13 | pub inner: TransactionReceipt, 14 | /// Address of the contract. 15 | pub contract_address: Address, 16 | } 17 | -------------------------------------------------------------------------------- /lib/e2e/src/system.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | network::{Ethereum, EthereumWallet}, 3 | primitives::Address, 4 | providers::{ 5 | fillers::{ 6 | BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, 7 | NonceFiller, WalletFiller, 8 | }, 9 | Identity, RootProvider, 10 | }, 11 | transports::http::{Client, Http}, 12 | }; 13 | use eyre::bail; 14 | 15 | use crate::environment::get_node_path; 16 | 17 | pub(crate) const RPC_URL_ENV_VAR_NAME: &str = "RPC_URL"; 18 | /// StylusDeployer contract address. 19 | pub const DEPLOYER_ADDRESS: &str = "DEPLOYER_ADDRESS"; 20 | 21 | /// Convenience type alias that represents an Ethereum wallet. 22 | pub type Wallet = FillProvider< 23 | JoinFill< 24 | JoinFill< 25 | Identity, 26 | JoinFill< 27 | GasFiller, 28 | JoinFill>, 29 | >, 30 | >, 31 | WalletFiller, 32 | >, 33 | RootProvider>, 34 | Http, 35 | Ethereum, 36 | >; 37 | 38 | /// Send `amount` eth to `address` in the nitro-tesnode. 39 | /// 40 | /// # Errors 41 | /// 42 | /// May fail if unable to find the path to the node or if funding the newly 43 | /// created account fails. 44 | pub fn fund_account(address: Address, amount: u32) -> eyre::Result<()> { 45 | let node_script = get_node_path()?.join("test-node.bash"); 46 | if !node_script.exists() { 47 | bail!("Test nitro node wasn't setup properly. Try to setup it first with `./scripts/nitro-testnode.sh -i -d`") 48 | }; 49 | 50 | let output = std::process::Command::new(node_script) 51 | .arg("script") 52 | .arg("send-l2") 53 | .arg("--to") 54 | .arg(format!("address_{address}")) 55 | .arg("--ethamount") 56 | .arg(amount.to_string()) 57 | .output()?; 58 | 59 | if !output.status.success() { 60 | let err = String::from_utf8_lossy(&output.stderr); 61 | bail!("account's wallet wasn't funded - address is {address}:\n{err}") 62 | } 63 | 64 | Ok(()) 65 | } 66 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "docs/" 3 | command = "npm run docs" 4 | publish = "build/site" 5 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | # We should use stable here once nitro-testnode is updated and the contracts fit 3 | # the size limit (issue ). 4 | channel = "nightly-2025-06-03" 5 | components = ["rust-src"] 6 | targets = ["wasm32-unknown-unknown"] 7 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | format_macro_matchers = true 3 | group_imports = "StdExternalCrate" 4 | imports_granularity = "Crate" 5 | reorder_impl_items = true 6 | use_field_init_shorthand = true 7 | use_small_heuristics = "Max" 8 | wrap_comments = true 9 | -------------------------------------------------------------------------------- /scripts/bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | MYDIR=$(realpath "$(dirname "$0")") 5 | cd "$MYDIR" 6 | cd .. 7 | 8 | # Optimize contract's wasm binary by crate name. 9 | opt_wasm() { 10 | local CONTRACT_CRATE_NAME=$1 11 | local CONTRACT_BIN_NAME="${CONTRACT_CRATE_NAME//-/_}.wasm" 12 | local CONTRACT_OPT_BIN_NAME="${CONTRACT_CRATE_NAME//-/_}_opt.wasm" 13 | 14 | echo 15 | echo "Optimizing $CONTRACT_CRATE_NAME WASM binary" 16 | # https://rustwasm.github.io/book/reference/code-size.html#use-the-wasm-opt-tool 17 | wasm-opt --enable-bulk-memory -O3 -o ./target/wasm32-unknown-unknown/release/"$CONTRACT_OPT_BIN_NAME" ./target/wasm32-unknown-unknown/release/"$CONTRACT_BIN_NAME" 18 | } 19 | 20 | # Retrieve all alphanumeric contract's crate names in `./examples` directory. 21 | get_example_crate_names() { 22 | # shellcheck disable=SC2038 23 | # NOTE: optimistically relying on the 'name = ' string at Cargo.toml file 24 | find ./examples -maxdepth 2 -type f -name "Cargo.toml" | xargs grep 'name = ' | grep -oE '".*"' | tr -d "'\"" 25 | } 26 | 27 | cargo build --release --target wasm32-unknown-unknown \ 28 | -Z build-std=std,panic_abort \ 29 | -Z build-std-features=panic_immediate_abort 30 | 31 | # Optimize contract's wasm for gas usage. 32 | for CRATE_NAME in $(get_example_crate_names); do 33 | opt_wasm "$CRATE_NAME" 34 | done 35 | 36 | export RPC_URL=http://localhost:8547 37 | export DEPLOYER_ADDRESS=0x6ac4839Bfe169CadBBFbDE3f29bd8459037Bf64e 38 | 39 | # No need to compile benchmarks with `--release` 40 | # since this only runs the benchmarking code and the contracts have already been compiled with `--release`. 41 | cargo run -p benches 42 | echo "This benchmarks measure gas execution cost, 43 | the 21000 EVM base gas fee is omitted." 44 | echo 45 | echo "To measure non cached contract's gas usage correctly, 46 | benchmarks should run on a clean instance of the nitro test node." 47 | echo 48 | echo "Finished running benches!" 49 | -------------------------------------------------------------------------------- /scripts/check-abi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Function to retrieve all Cargo.toml paths in the ./examples directory 5 | get_example_dirs() { 6 | find ./examples -maxdepth 2 -type f -name "Cargo.toml" | xargs -n1 dirname | sort 7 | } 8 | 9 | # Get the root directory of the git repository 10 | ROOT_DIR=$(git rev-parse --show-toplevel) 11 | cd "$ROOT_DIR" || exit 12 | 13 | # Check contract ABI by crate name 14 | check_abi() { 15 | local CRATE_PATH=$1 16 | 17 | echo "Checking contract $CRATE_PATH" 18 | 19 | echo 20 | 21 | cd "$CRATE_PATH" 22 | 23 | cargo stylus export-abi 24 | 25 | echo 26 | 27 | echo "Done!" 28 | 29 | echo 30 | 31 | cd "$ROOT_DIR" 32 | } 33 | 34 | 35 | for CRATE_PATH in $(get_example_dirs); do 36 | check_abi "$CRATE_PATH" 37 | done 38 | -------------------------------------------------------------------------------- /scripts/check-wasm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | mydir=$(dirname "$0") 5 | cd "$mydir" || exit 6 | cd .. 7 | 8 | # Check contract wasm binary by crate name 9 | check_wasm() { 10 | local CONTRACT_CRATE_NAME=$1 11 | local CONTRACT_BIN_NAME="${CONTRACT_CRATE_NAME//-/_}.wasm" 12 | 13 | echo 14 | echo "Checking contract $CONTRACT_CRATE_NAME" 15 | cargo stylus check -e https://sepolia-rollup.arbitrum.io/rpc --wasm-file ./target/wasm32-unknown-unknown/release/"$CONTRACT_BIN_NAME" 16 | } 17 | 18 | # Retrieve all alphanumeric contract's crate names in `./examples` directory. 19 | get_example_crate_names() { 20 | # shellcheck disable=SC2038 21 | # NOTE: optimistically relying on the 'name = ' string at Cargo.toml file 22 | find ./examples -maxdepth 2 -type f -name "Cargo.toml" | xargs grep 'name = ' | grep -oE '".*"' | tr -d "'\"" 23 | } 24 | 25 | cargo build --release --target wasm32-unknown-unknown \ 26 | -Z build-std=std,panic_abort \ 27 | -Z build-std-features=panic_immediate_abort 28 | 29 | for CRATE_NAME in $(get_example_crate_names); do 30 | check_wasm "$CRATE_NAME" 31 | sleep 2 32 | done 33 | -------------------------------------------------------------------------------- /scripts/e2e-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Navigate to project root 5 | cd "$(dirname "$(realpath "$0")")/.." 6 | 7 | cargo build --release --target wasm32-unknown-unknown \ 8 | -Z build-std=std,panic_abort \ 9 | -Z build-std-features=panic_immediate_abort 10 | 11 | export RPC_URL=http://localhost:8547 12 | export DEPLOYER_ADDRESS=0x6ac4839Bfe169CadBBFbDE3f29bd8459037Bf64e 13 | 14 | # If any arguments are set, just pass them as-is to the cargo test command 15 | if [[ $# -eq 0 ]]; then 16 | cargo test --features e2e --test "*" 17 | else 18 | cargo test --features e2e "$@" 19 | fi 20 | -------------------------------------------------------------------------------- /scripts/nitro-testnode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MYDIR=$(realpath "$(dirname "$0")") 4 | cd "$MYDIR" || exit 5 | 6 | HAS_INIT=false 7 | HAS_DETACH=false 8 | 9 | while [[ $# -gt 0 ]]; do 10 | case "$1" in 11 | -i | --init) 12 | HAS_INIT=true 13 | shift 14 | ;; 15 | -d | --detach) 16 | HAS_DETACH=true 17 | shift 18 | ;; 19 | -q | --quit) 20 | NITRO_CONTAINERS=$(docker container ls -q --filter name=nitro-testnode) 21 | 22 | if [ -z "$NITRO_CONTAINERS" ]; then 23 | echo "No nitro-testnode containers running" 24 | else 25 | docker container stop $NITRO_CONTAINERS || exit 26 | fi 27 | 28 | exit 0 29 | ;; 30 | *) 31 | echo "OPTIONS:" 32 | echo "-i|--init: clone repo and init nitro test node" 33 | echo "-d|--detach: setup nitro test node in detached mode" 34 | echo "-q|--quit: shutdown nitro test node docker containers" 35 | exit 0 36 | ;; 37 | esac 38 | done 39 | 40 | TEST_NODE_DIR="$MYDIR/../nitro-testnode" 41 | if [ ! -d "$TEST_NODE_DIR" ]; then 42 | HAS_INIT=true 43 | fi 44 | 45 | if $HAS_INIT; then 46 | cd "$MYDIR" || exit 47 | cd .. 48 | 49 | git clone --recurse-submodules https://github.com/OffchainLabs/nitro-testnode.git --branch v3-support 50 | cd ./nitro-testnode || exit 51 | git pull origin release --recurse-submodules 52 | git checkout 1fe1b72bd33cb5bd862c04447435f1c159ff7a3f || exit 53 | 54 | ./test-node.bash --no-run --init || exit 55 | fi 56 | 57 | cd "$TEST_NODE_DIR" || exit 58 | if $HAS_DETACH; then 59 | ./test-node.bash --detach 60 | else 61 | ./test-node.bash 62 | fi 63 | --------------------------------------------------------------------------------