Skip to content

Commit fe15887

Browse files
authored
Fix Cantina audit issues (#3)
* Start to fix audit feedback * Add interface * Fix test * Cleanup * Inherit from ICFG
1 parent 0b51f63 commit fe15887

6 files changed

Lines changed: 56 additions & 9 deletions

File tree

script/CFG.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ contract CFGScript is Script, CreateXScript {
2525

2626
// Setup
2727
cfg.mint(mintDestination, initialMint);
28-
// cfg.deny(address(this)); // TODO
28+
cfg.deny(address(this));
2929

3030
vm.stopBroadcast();
3131
}

src/CFG.sol

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
pragma solidity 0.8.28;
33

44
import {DelegationToken} from "src/DelegationToken.sol";
5+
import {ICFG} from "src/interfaces/ICFG.sol";
56

67
/// @title Centrifuge Token
7-
contract CFG is DelegationToken {
8+
contract CFG is DelegationToken, ICFG {
89
constructor(address ward) DelegationToken(18) {
910
file("name", "Centrifuge");
1011
file("symbol", "CFG");
@@ -18,10 +19,11 @@ contract CFG is DelegationToken {
1819

1920
unchecked {
2021
// We don't need overflow checks b/c require(balance >= value) and balance <= totalSupply
21-
_setBalance(msg.sender, _balanceOf(msg.sender) - value);
22-
totalSupply = totalSupply - value;
22+
_setBalance(msg.sender, balance - value);
23+
totalSupply -= value;
2324
}
2425

26+
_moveDelegateVotes(delegatee[msg.sender], address(0), value);
2527
emit Transfer(msg.sender, address(0), value);
2628
}
2729
}

src/DelegationToken.sol

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity 0.8.28;
33

44
import {ERC20} from "protocol-v3/misc/ERC20.sol";
5+
import {IERC20} from "protocol-v3/misc/interfaces/IERC20.sol";
56
import {IDelegationToken, Delegation, Signature} from "src/interfaces/IDelegationToken.sol";
67

78
/// @title Delegation Token
@@ -14,7 +15,7 @@ import {IDelegationToken, Delegation, Signature} from "src/interfaces/IDelegatio
1415
///
1516
/// By default, token balance does not account for voting power. This makes transfers cheaper. Whether
1617
/// an account has to self-delegate to vote depends on the voting contract implementation.
17-
/// @author Modified from https://github.com/morpho-org/morpho-token-upgradeable
18+
/// @author Modified from https://github.com/morpho-org/morpho-token
1819
contract DelegationToken is ERC20, IDelegationToken {
1920
bytes32 public constant DELEGATION_TYPEHASH =
2021
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
@@ -42,6 +43,7 @@ contract DelegationToken is ERC20, IDelegationToken {
4243
abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), keccak256(abi.encode(DELEGATION_TYPEHASH, delegation)))
4344
);
4445
address delegator = ecrecover(digest, signature.v, signature.r, signature.s);
46+
require(delegator != address(0), InvalidSignature());
4547
require(delegation.nonce == delegationNonce[delegator]++, InvalidDelegationNonce());
4648

4749
_delegate(delegator, delegation.delegatee);
@@ -57,13 +59,17 @@ contract DelegationToken is ERC20, IDelegationToken {
5759
}
5860

5961
/// @dev Moves voting power when tokens are transferred.
60-
function transfer(address to, uint256 value) public override(ERC20) returns (bool success) {
62+
function transfer(address to, uint256 value) public override(ERC20, IERC20) returns (bool success) {
6163
success = super.transfer(to, value);
6264
_moveDelegateVotes(delegatee[msg.sender], delegatee[to], value);
6365
}
6466

6567
/// @dev Moves voting power when tokens are transferred.
66-
function transferFrom(address from, address to, uint256 value) public override(ERC20) returns (bool success) {
68+
function transferFrom(address from, address to, uint256 value)
69+
public
70+
override(ERC20, IERC20)
71+
returns (bool success)
72+
{
6773
success = super.transferFrom(from, to, value);
6874
_moveDelegateVotes(delegatee[from], delegatee[to], value);
6975
}

src/interfaces/ICFG.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity 0.8.28;
3+
4+
import {IDelegationToken} from "src/interfaces/IDelegationToken.sol";
5+
6+
interface ICFG is IDelegationToken {
7+
/// @notice Burns sender's tokens.
8+
function burn(uint256 value) external;
9+
}

src/interfaces/IDelegationToken.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22
pragma solidity 0.8.28;
33

4+
import {IERC20Metadata, IERC20Permit} from "protocol-v3/misc/interfaces/IERC20.sol";
5+
46
struct Delegation {
57
address delegatee;
68
uint256 nonce;
@@ -13,7 +15,7 @@ struct Signature {
1315
bytes32 s;
1416
}
1517

16-
interface IDelegationToken {
18+
interface IDelegationToken is IERC20Metadata, IERC20Permit {
1719
/// @notice Emitted when an delegator changes their delegatee.
1820
event DelegateeChanged(address indexed delegator, address indexed oldDelegatee, address indexed newDelegatee);
1921

@@ -26,6 +28,9 @@ interface IDelegationToken {
2628
/// @notice The delegation nonce used by the signer is not its current delegation nonce.
2729
error InvalidDelegationNonce();
2830

31+
/// @notice The signature was invalid.
32+
error InvalidSignature();
33+
2934
/// @notice Returns the delegatee that `account` has chosen.
3035
function delegatee(address account) external view returns (address);
3136

test/CFG.t.sol

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ contract CFGTest is Test {
5353
}
5454

5555
function testBurnSelf(address user, uint256 mintAmount, uint256 burnAmount) public {
56-
vm.assume(user != address(this) && user != address(0));
56+
vm.assume(user != address(this) && user != address(0) && user != address(token));
5757
burnAmount = bound(burnAmount, 0, mintAmount);
5858

5959
assertEq(token.wards(user), 0);
@@ -72,6 +72,7 @@ contract CFGTest is Test {
7272
uint256 transferAmount,
7373
uint256 transferFromAmount,
7474
uint256 burnAmount,
75+
uint256 burnPermissionlessAmount,
7576
address delegatee,
7677
address user2,
7778
address delegatee2
@@ -84,6 +85,7 @@ contract CFGTest is Test {
8485
transferAmount = bound(transferAmount, 0, mintAmount);
8586
transferFromAmount = bound(transferFromAmount, 0, mintAmount - transferAmount);
8687
burnAmount = bound(burnAmount, 0, mintAmount - transferAmount - transferFromAmount);
88+
burnPermissionlessAmount = bound(burnPermissionlessAmount, 0, transferAmount + transferFromAmount);
8789

8890
token.mint(address(this), mintAmount);
8991
assertEq(token.balanceOf(address(this)), mintAmount);
@@ -122,6 +124,14 @@ contract CFGTest is Test {
122124
assertEq(token.delegatee(user2), delegatee2);
123125
assertEq(token.delegatedVotingPower(delegatee), mintAmount - transferAmount - transferFromAmount - burnAmount);
124126
assertEq(token.delegatedVotingPower(delegatee2), transferAmount + transferFromAmount);
127+
128+
vm.prank(user2);
129+
token.burn(burnPermissionlessAmount);
130+
131+
assertEq(token.delegatee(address(this)), delegatee);
132+
assertEq(token.delegatee(user2), delegatee2);
133+
assertEq(token.delegatedVotingPower(delegatee), mintAmount - transferAmount - transferFromAmount - burnAmount);
134+
assertEq(token.delegatedVotingPower(delegatee2), transferAmount + transferFromAmount - burnPermissionlessAmount);
125135
}
126136

127137
function testDelegateWithSig(address delegatee) public {
@@ -174,5 +184,20 @@ contract CFGTest is Test {
174184

175185
vm.expectRevert(IDelegationToken.DelegatesExpiredSignature.selector);
176186
token.delegateWithSig(delegation, Signature(v, r, s));
187+
188+
// Cannot use invalid signature
189+
delegation = Delegation(delegatee, token.delegationNonce(owner), block.timestamp);
190+
191+
(v, r, s) = vm.sign(
192+
privateKey,
193+
keccak256(
194+
abi.encodePacked(
195+
"\x19\x02", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(token.DELEGATION_TYPEHASH(), delegation))
196+
)
197+
)
198+
);
199+
200+
vm.expectRevert(IDelegationToken.InvalidSignature.selector);
201+
token.delegateWithSig(delegation, Signature(v, bytes32(""), bytes32("")));
177202
}
178203
}

0 commit comments

Comments
 (0)