Omniscia Steer Protocol Audit

TreasuryVester Manual Review Findings

TreasuryVester Manual Review Findings

TVR-01M: Improper Change of Recipient

TypeSeverityLocation
Logical FaultTreasuryVester.sol:L46-L56

Description:

The setRecipient function is meant to change the recipient of the vesting process, however, this is done improperly so as the funds are not re-delegated to the new recipient.

Impact:

It is currently possible to "sell" a treasury vest to another user whilst retaining the delegated voting power if the buyer is an unsuspecting user.

Example:

contracts/TreasuryVester.sol
46/// @dev Change the recipient address. Can only be called by current recipient.
47/// @param _recipient is the new recipient address
48function setRecipient(address _recipient) external {
49 require(_recipient != address(0), "Zero address, Invalid!");
50 require(
51 // Make sure current recipient is sending this
52 msg.sender == recipient,
53 "R"
54 );
55 recipient = _recipient;
56}
57
58function claim() external {
59 require(msg.sender == recipient, 'R');
60 _claim();
61}
62
63/// @dev If the user leaves steer then steer can cut off the user's funds from that point on.
64function cutOffFunds() external {
65 require(msg.sender == owner);
66 if (block.timestamp >= vestingCliff) {
67 _claim();
68 }
69
70 IERC20(steerToken).safeTransfer(
71 owner,
72 IERC20(steerToken).balanceOf(address(this))
73 );
74}
75
76/// @dev User can call this function to get the voting power of the tokens that are vested.
77function getVotingPowerForVestedTokens() external {
78 ISteerToken(steerToken).delegate(recipient);
79}

Recommendation:

We advise the setRecipient function to also invoke the getVotingPowerForVestedTokens function to ensure that the tokens do not remain incorrectly delegated to the previous recipient.

Alleviation (200f275c40cbd4798f4a416c044ea726755d4741):

The setRecipient function now properly invokes getVotingPowerForVestedTokens, ensuring that the voting power is transferred whenever a recipient changes.

TVR-02M: Inexistent Guarantee of Vested Funds

TypeSeverityLocation
Logical FaultTreasuryVester.sol:L26

Description:

The TreasuryVester contract does not pull any funds from its creator thus providing no guarantee that the vest will be claimable.

Impact:

If the vesting contract hasn't been sufficiently funded, any funds that were not claimed until the vested amount exceeded the contract's owned amount will not be claimable.

Example:

contracts/TreasuryVester.sol
22constructor(
23 address steerToken_,
24 address recipient_,
25 address _owner, // Intended to be multisig. This address can cut off funding if user leaves Steer.
26 uint256 vestingAmount_,
27 uint256 vestingBegin_,
28 uint256 vestingCliff_,
29 uint256 vestingEnd_
30) {
31 require(vestingCliff_ >= vestingBegin_, "C");
32 require(vestingEnd_ > vestingCliff_, "E");
33
34 steerToken = steerToken_;
35 recipient = recipient_;
36 owner = _owner;
37
38 vestingAmount = vestingAmount_;
39 vestingBegin = vestingBegin_;
40 vestingCliff = vestingCliff_;
41 vestingEnd = vestingEnd_;
42
43 lastUpdate = vestingBegin;
44}

Recommendation:

We advise the constructor of the contract to instead execute a safeTransferFrom invocation from its creator ensuring that the contract has been initialized with the vestingAmount_ properly.

Alleviation (200f275c40cbd4798f4a416c044ea726755d4741):

The Steer Protocol team stated that they will make sure to fund the contract accordingly. As a result, we consider this exhibit acknowledged.