Skip to content

feat: add token allow list smart contract with tests #2

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

Merged
merged 21 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6f6bbed
chore: include ape compile build artifacts
lumoswiz Feb 3, 2025
438f229
chore: pin solidity version and add solady dependency
lumoswiz Feb 3, 2025
3776e1a
chore: remove hello.py
lumoswiz Feb 3, 2025
8114373
feat: add simple token allowlist contract
lumoswiz Feb 3, 2025
607b81d
chore: remove placeholder test_add.py
lumoswiz Feb 3, 2025
8f78591
test: add basic token allowlist tests
lumoswiz Feb 3, 2025
146d7b6
feat: add owner modifier to allowlist functions
lumoswiz Feb 3, 2025
4cf0ae6
ci: add contract compilation step and ape plugins to github workflow
lumoswiz Feb 3, 2025
483bc74
ci: install solady dependency before running tests
lumoswiz Feb 3, 2025
bd4ca48
fix: solady dependency tag
lumoswiz Feb 3, 2025
589cbbf
ci: fix solady install syntax
lumoswiz Feb 3, 2025
1f25612
ci: add debug steps for contract compilation
lumoswiz Feb 3, 2025
52bc719
ci: explicitly install ape plugins before compilation
lumoswiz Feb 3, 2025
30cbe85
ci: simplify plugin installation using ape config
lumoswiz Feb 3, 2025
1766102
ci: simplify workflow and use direct plugin installation
lumoswiz Feb 3, 2025
9dc1f16
ci: install eth-ape before plugins
lumoswiz Feb 3, 2025
d321b84
ci: project python env
lumoswiz Feb 3, 2025
241398e
ci: add workflow triggers for automated runs
lumoswiz Feb 3, 2025
b495ff0
ci: set uv system python var
lumoswiz Feb 3, 2025
3d6de36
ci: remove include dependencies from ape compile
lumoswiz Feb 3, 2025
00590eb
test: add removal and authorization tests to allowlist
lumoswiz Feb 3, 2025
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
28 changes: 22 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: CI

on:
workflow_dispatch:
pull_request:
Expand All @@ -7,24 +8,39 @@ on:
- 'main'

jobs:
test:
uv-example:
name: python
runs-on: ubuntu-latest
env:
UV_SYSTEM_PYTHON: 1

steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v5
- uses: actions/setup-python@v5
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: '0.5.26'

- name: 'Set up Python'
uses: actions/setup-python@v5
with:
python-version-file: 'pyproject.toml'

- name: Install dependencies
- name: Install project and dependencies
run: uv sync --all-extras --dev

- uses: astral-sh/ruff-action@v3

- uses: ApeWorX/github-action@v3
with:
python-version: '3.11'
ape-version-pin: '0.8.25'
ape-plugins-list: 'solidity==0.8.5 alchemy==0.8.7 etherscan==0.8.4 foundry==0.8.7'

- name: Install contract dependencies
run: ape pm install gh:Vectorized/solady --name solady --ref v0.1.3

- name: Compile contracts
run: ape compile --force

- name: Run tests
run: ape test -s
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ wheels/
# Virtual environments
.venv

# Unit test
.pytest_cache/
# Ape
.pytest_cache/
.build/
8 changes: 8 additions & 0 deletions ape-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@ plugins:
version: 0.8.7
- name: solidity
version: 0.8.5

solidity:
version: 0.8.25

dependencies:
- name: Solady
github: Vectorized/solady
ref: v0.1.3
43 changes: 43 additions & 0 deletions contracts/TokenAllowlist.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {Ownable} from "solady/src/auth/Ownable.sol";
import {EnumerableSetLib} from "solady/src/utils/EnumerableSetLib.sol";

contract TokenAllowlist is Ownable {
using EnumerableSetLib for EnumerableSetLib.AddressSet;

EnumerableSetLib.AddressSet internal allowList;

constructor() {
_initializeOwner(msg.sender);
}

function addToken(address token) external onlyOwner {
allowList.add(token);
}

function addTokensBatch(address[] memory tokens) external onlyOwner {
for (uint256 i; i < tokens.length; ++i) {
allowList.add(tokens[i]);
}
}

function removeToken(address token) external onlyOwner {
allowList.remove(token);
}

function removeTokensBatch(address[] memory tokens) external onlyOwner {
for (uint256 i; i < tokens.length; ++i) {
allowList.remove(tokens[i]);
}
}

function allowedTokens() external view returns (address[] memory) {
return allowList.values();
}

function isAllowed(address token) external view returns (bool) {
return allowList.contains(token);
}
}
6 changes: 0 additions & 6 deletions hello.py

This file was deleted.

2 changes: 0 additions & 2 deletions tests/test_add.py

This file was deleted.

105 changes: 105 additions & 0 deletions tests/test_allowlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import ape
import pytest


# Gnosis chain token addresses
class TokenAddresses:
GNO = "0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb"
COW = "0x177127622c4A00F3d409B75571e12cB3c8973d3c"
WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
SAFE = "0x5aFE3855358E112B5647B952709E6165e1c1eEEe"


@pytest.fixture
def tokens():
return TokenAddresses()


@pytest.fixture
def token_batch(tokens):
return [tokens.GNO, tokens.COW, tokens.WETH, tokens.SAFE]


@pytest.fixture
def owner(accounts):
return accounts[0]


@pytest.fixture
def not_owner(accounts):
return accounts[1]


@pytest.fixture
def allowlist_contract(owner, project):
return owner.deploy(project.TokenAllowlist)


@pytest.fixture
def populated_allowlist(allowlist_contract, owner, token_batch):
allowlist_contract.addTokensBatch(token_batch, sender=owner)
return allowlist_contract


def test_add_token(allowlist_contract, tokens, owner, not_owner):
allowlist_contract.addToken(tokens.GNO, sender=owner)
allowed = allowlist_contract.isAllowed(tokens.GNO)
assert allowed is True

with ape.reverts():
allowlist_contract.addToken(tokens.GNO, sender=not_owner)


def test_token_not_allowed(allowlist_contract, tokens, owner):
allowlist_contract.addToken(tokens.GNO, sender=owner)
allowed = allowlist_contract.isAllowed(tokens.COW)
assert allowed is False


def test_add_batch_tokens(allowlist_contract, token_batch, owner, not_owner):
allowlist_contract.addTokensBatch(token_batch, sender=owner)
for token in token_batch:
allowed = allowlist_contract.isAllowed(token)
assert allowed is True

with ape.reverts():
allowlist_contract.addTokensBatch(token_batch, sender=not_owner)


def test_allowed_tokens(allowlist_contract, token_batch, owner):
allowlist_contract.addTokensBatch(token_batch, sender=owner)
allowed_tokens = allowlist_contract.allowedTokens()
assert allowed_tokens == token_batch


def test_remove_token(populated_allowlist, tokens, owner, not_owner):
populated_allowlist.removeToken(tokens.GNO, sender=owner)
allowed = populated_allowlist.isAllowed(tokens.GNO)
assert allowed is False

expected_tokens = [tokens.COW, tokens.WETH, tokens.SAFE]
allowed_tokens = populated_allowlist.allowedTokens()
assert set(allowed_tokens) == set(expected_tokens)

with ape.reverts():
populated_allowlist.removeToken(tokens.GNO, sender=not_owner)


def test_remove_batch_tokens(populated_allowlist, tokens, owner, not_owner):
tokens_to_remove = [tokens.GNO, tokens.WETH]
populated_allowlist.removeTokensBatch(tokens_to_remove, sender=owner)

for token in tokens_to_remove:
allowed = populated_allowlist.isAllowed(token)
assert allowed is False

remaining_tokens = [tokens.COW, tokens.SAFE]
for token in remaining_tokens:
allowed = populated_allowlist.isAllowed(token)
assert allowed is True

allowed_tokens = populated_allowlist.allowedTokens()
assert set(allowed_tokens) == set(remaining_tokens)

with ape.reverts():
populated_allowlist.removeTokensBatch(tokens_to_remove, sender=not_owner)