Skip to content
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
22 changes: 22 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
node_modules
.env
dist

# Hardhat files
/cache
/artifacts

# TypeChain files
/typechain
/typechain-types

# solidity-coverage files
/coverage
/coverage.json

gas_report

contracts/vendor

# Hardhat deploy artifacts
/deployments
13 changes: 13 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
extends: ['plugin:@api3/eslint-plugin-commons/universal', 'plugin:@api3/eslint-plugin-commons/jest'],
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
rules: {
camelcase: 'off',
'functional/no-try-statements': 'off',
'unicorn/filename-case': 'off',
},
ignorePatterns: ['typechain-types/*'],
};
7 changes: 7 additions & 0 deletions .github/config/linkspector.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
aliveStatusCodes:
- 200
- 406
dirs:
- .
- .github
useGitIgnore: true
16 changes: 16 additions & 0 deletions .github/workflows/check-md-links.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Check Markdown links

on: push

jobs:
check-md-links:
runs-on: ubuntu-latest
steps:
- name: Clone @api3/data-feed-proxy-combinators
uses: actions/checkout@v6
- name: Check Markdown links
uses: umbrelladocs/action-linkspector@v1
with:
reporter: github-pr-review
config_file: .github/config/linkspector.yml
fail_on_error: true
40 changes: 40 additions & 0 deletions .github/workflows/continuous-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Continuous build

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
lint-build-test:
runs-on: ubuntu-latest
steps:
- name: Clone @api3/humpy-comp
uses: actions/checkout@v6

- name: Set up pnpm
uses: pnpm/action-setup@v3

- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'pnpm'

- name: Install dependencies
run: pnpm install

- name: Build
run: pnpm build

- name: Type check
run: pnpm tsc

- name: Lint
run: pnpm lint

- name: Test
run: pnpm test
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
node_modules
.env
dist

# Hardhat files
/cache
/artifacts

# TypeChain files
/typechain
/typechain-types

# solidity-coverage files
/coverage
/coverage.json

# Hardhat deploy artifacts
/deployments
22 changes: 22 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
node_modules
.env
dist

# Hardhat files
/cache
/artifacts

# TypeChain files
/typechain
/typechain-types

# solidity-coverage files
/coverage
/coverage.json

gas_report

contracts/vendor

# Hardhat deploy artifacts
/deployments
27 changes: 27 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"bracketSpacing": true,
"printWidth": 120,
"singleQuote": true,
"trailingComma": "es5",
"useTabs": false,
"plugins": ["prettier-plugin-solidity"],
"overrides": [
{
"files": "*.md",
"options": {
"parser": "markdown"
}
},
{
"files": "*.sol",
"options": {
"parser": "slang",
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false
}
}
]
}
13 changes: 13 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "solhint:recommended",
"rules": {
"compiler-version": "off",
"func-visibility": ["warn", { "ignoreConstructors": true }],
"not-rely-on-time": "off",
"no-empty-blocks": "off",
"no-global-import": "off",
"func-name-mixedcase": "off",
"immutable-vars-naming": "off",
"gas-custom-errors": "off"
}
}
19 changes: 19 additions & 0 deletions .solhintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
node_modules
.env
dist

# Hardhat files
/cache
/artifacts

# TypeChain files
/typechain
/typechain-types

# solidity-coverage files
/coverage
/coverage.json

gas_report

./contracts/vendor
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Humpy Comp

ERC20 wrapper for COMP token that delegates voting power to another address.

Set the required environment variable before deploying:

Deploy on Ethereum:

```
INITIAL_DELEGATEE=0xDelegateeAddress pnpm deploy:humpy-comp:ethereum
```

Override initial owner:

```
INITIAL_DELEGATEE=0xDelegateeAddress OWNER=0xOwnerAddress pnpm run deploy:humpy-comp:ethereum
```

Deploy to a local mainnet fork (running on `localhost`) with:

```
INITIAL_DELEGATEE=0xDelegateeAddress pnpm deploy:humpy-comp:localhost
```
70 changes: 70 additions & 0 deletions contracts/HumpyComp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import {
Ownable,
Ownable2Step
} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC20Wrapper} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
import {IComp} from "./vendor/@compound-finance/compound-governance/contracts/interfaces/IComp.sol";

/// @title Humpy COMP
/// @author API3 DAO
/// @notice 1:1 wrapper token for COMP built on OpenZeppelin ERC20Wrapper
contract HumpyComp is ERC20Wrapper, Ownable2Step {

Choose a reason for hiding this comment

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

Let's call it dComp instead (name and symbol can be the same)

address internal constant COMP_ADDRESS =
0xc00e94Cb662C3520282E6f5717214004A7f26888;
IComp internal immutable comp = IComp(COMP_ADDRESS);

/// @notice Constructs the Humpy COMP wrapper
/// @param initialOwner Address of the initial owner
/// @param initialDelegatee Address of the initial delegatee for COMP voting power
constructor(
address initialOwner,

Choose a reason for hiding this comment

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

Also, we'll likely set the owner to the address that Humpy (the borrower) will tell us. Better for us not to manage anything in this contract.

address initialDelegatee
)
ERC20("Humpy COMP", "HumpyCOMP")
ERC20Wrapper(IERC20(COMP_ADDRESS))
Ownable(initialOwner)
{
_setDelegatee(initialDelegatee);
}

/// @notice Returns the current COMP delegatee for voting power held by this wrapper
/// @return delegateeAddress Current delegatee address
function delegatee() external view returns (address) {
return comp.delegates(address(this));
}

/// @notice Wraps COMP tokens for the caller at a 1:1 ratio
/// @param value Amount of COMP to deposit
/// @return success Whether wrapping succeeded
function deposit(uint256 value) external returns (bool) {
return depositFor(msg.sender, value);
}

/// @notice Unwraps HumpyCOMP tokens for the caller at a 1:1 ratio
/// @param value Amount of HumpyCOMP to burn and withdraw as COMP
/// @return success Whether unwrapping succeeded
function withdraw(uint256 value) external returns (bool) {
return withdrawTo(msg.sender, value);
}

/**
* @notice Allows the owner to change who receives the aggregated voting power
* @param newDelegatee The new address to receive the COMP voting power
*/
function setDelegatee(address newDelegatee) external onlyOwner {
_setDelegatee(newDelegatee);
}

/**
* @notice Updates delegated voting power recipient for COMP held by this wrapper
* @param newDelegatee New delegatee address
*/
function _setDelegatee(address newDelegatee) internal {
comp.delegate(newDelegatee);
}
}
13 changes: 13 additions & 0 deletions contracts/test/MockComp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Comp} from "../vendor/@compound-finance/compound-governance/contracts/Comp.sol";

contract MockComp is Comp {
constructor(address account) Comp(account) {}

function mint(address to, uint96 amount) external {
balances[to] += amount;
_moveDelegates(delegates[address(0)], delegates[to], amount);
}
}
Loading