Skip to content

solidity link examples #27

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/lib/ExclusiveDelegateResolver"]
path = contracts/lib/ExclusiveDelegateResolver
url = https://github.com/yuga-labs/ExclusiveDelegateResolver
[submodule "contracts/lib/solady"]
path = contracts/lib/solady
url = https://github.com/vectorized/solady
45 changes: 45 additions & 0 deletions contracts/.github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

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

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

- name: Show Forge version
run: |
forge --version

- name: Run Forge fmt
run: |
forge fmt --check
id: fmt

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

- name: Run Forge tests
run: |
forge test -vvv
id: test
14 changes: 14 additions & 0 deletions contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

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

# Docs
docs/

# Dotenv file
.env
66 changes: 66 additions & 0 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Foundry

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

Foundry consists of:

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

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

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

### Cast

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

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
6 changes: 6 additions & 0 deletions contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions contracts/lib/ExclusiveDelegateResolver
1 change: 1 addition & 0 deletions contracts/lib/forge-std
Submodule forge-std added at b93cf4
1 change: 1 addition & 0 deletions contracts/lib/solady
Submodule solady added at 8583a6
89 changes: 89 additions & 0 deletions contracts/src/AllowlistMint721.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import {ERC721} from "solady/tokens/ERC721.sol";
import {MerkleProofLib} from "solady/utils/MerkleProofLib.sol";
import {IExclusiveDelegateResolver} from "ExclusiveDelegateResolver/src/IExclusiveDelegateResolver.sol";
// @dev use zkSync specific import for signature verification
import {SignatureCheckerLib} from "solady/utils/ext/zksync/SignatureCheckerLib.sol";

contract AllowlistMint721 is ERC721 {
error AlreadyMinted();
error NotAllowed();
error InvalidProof();
error InvalidSignature();

bytes24 constant _AGW_LINK_RIGHTS = bytes24(keccak256("AGW_LINK"));

IExclusiveDelegateResolver public constant DELEGATE_RESOLVER =
IExclusiveDelegateResolver(0x0000000078CC4Cc1C14E27c0fa35ED6E5E58825D);

bytes32 public merkleRoot;
address public exampleSigner;

mapping(address => bool) public minted;

uint256 public tokenIndex;

constructor(bytes32 _merkleRoot, address _exampleSigner) {
merkleRoot = _merkleRoot;
exampleSigner = _exampleSigner;
}

function name() public pure override returns (string memory) {
return "Example";
}

function symbol() public pure override returns (string memory) {
return "EXAMPLE";
}

function tokenURI(uint256 tokenId) public pure override returns (string memory) {
return "";
}

function mintWithMerkleProof(address user, bytes32[] calldata proof) public {
// check if the address has already minted
if (minted[user]) revert AlreadyMinted();

// in this example, we want to allow the user ONLY to mint from a delegated AGW if it is set
// check that the caller is the current exclusively delegated wallet by rights
if (msg.sender != DELEGATE_RESOLVER.exclusiveWalletByRights(user, _AGW_LINK_RIGHTS)) {
revert NotAllowed();
}

// check the proof against the passed address as that is the address submitted
// for allowlisting (likely an EOA wallet)
if (!MerkleProofLib.verifyCalldata(proof, merkleRoot, keccak256(abi.encode(user)))) {
revert InvalidProof();
}

minted[user] = true;
_mint(msg.sender, ++tokenIndex);
}

function mintWithSignature(address user, bytes calldata signature) public {
if (minted[user]) revert AlreadyMinted();

// in this example, we want to allow the user to mint for themselves OR from a delegated AGW
// if the caller is not the user we are minting for; check if the caller is the exclusively
// delegated wallet for AGW linking
if (msg.sender != user) {
address allowedWallet = DELEGATE_RESOLVER.exclusiveWalletByRights(user, _AGW_LINK_RIGHTS);
if (msg.sender != allowedWallet) {
revert NotAllowed();
}
}

// check the signature against the passed address as that is the address submitted
// for allowlisting (likely an EOA wallet)
// @dev this is not a particular secure method of signature verification; would recommend using
// a more robust EIP-712 typed signature instead of just signing the hashed address
if (!SignatureCheckerLib.isValidSignatureNow(exampleSigner, keccak256(abi.encode(user)), signature)) {
revert InvalidSignature();
}

minted[user] = true;
_mint(msg.sender, ++tokenIndex);
}
}
49 changes: 49 additions & 0 deletions contracts/src/ERC20ClaimForHolders.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {IExclusiveDelegateResolver} from "ExclusiveDelegateResolver/src/IExclusiveDelegateResolver.sol";
import {LibBitmap} from "solady/utils/LibBitmap.sol";
// @dev use zkSync specific import for safe transfer
import {SafeTransferLib} from "solady/utils/ext/zksync/SafeTransferLib.sol";

contract ERC20ClaimForHolders {
using SafeTransferLib for address;
using LibBitmap for LibBitmap.Bitmap;

error NotExclusiveOwnerByRights();
error AlreadyClaimed();

uint256 public constant TOKENS_PER_NFT = 1e18;
address public NFT_CONTRACT_ADDRESS;
address public ERC20_CONTRACT_ADDRESS;

bytes24 constant _AGW_LINK_RIGHTS = bytes24(keccak256("AGW_LINK"));

IExclusiveDelegateResolver public constant DELEGATE_RESOLVER =
IExclusiveDelegateResolver(0x0000000078CC4Cc1C14E27c0fa35ED6E5E58825D);

LibBitmap.Bitmap private _claimedTokens;

function claimTokens(uint256 tokenId) external {
// check that the caller is the current exclusively delegated owner of this NFT by rights.
// If they are not, we are not going to let them claim the tokens.
// An alternative approach here would be to check that they are either the owner of the NFT
// and if they are not, check the delegation resolver to see if they are delegated to claim the tokens.
address owner = DELEGATE_RESOLVER.exclusiveOwnerByRights(NFT_CONTRACT_ADDRESS, tokenId, _AGW_LINK_RIGHTS);
if (msg.sender != owner) {
revert NotExclusiveOwnerByRights();
}

if (isClaimed(tokenId)) {
revert AlreadyClaimed();
}

_claimedTokens.set(tokenId);

ERC20_CONTRACT_ADDRESS.safeTransfer(msg.sender, TOKENS_PER_NFT);
}

function isClaimed(uint256 tokenId) public view returns (bool) {
return _claimedTokens.get(tokenId);
}
}