Skip to content

Commit 3bbf03e

Browse files
committed
feat: add reward caller signature POC
Add small untested POC for requiring a signature of the reward caller to prevent reward caller address grieving.
1 parent 66d1d84 commit 3bbf03e

File tree

2 files changed

+90
-23
lines changed

2 files changed

+90
-23
lines changed

contracts/bonding/BondingManager.sol

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,22 @@ import "../snapshots/IMerkleSnapshot.sol";
1515
import "./IBondingVotes.sol";
1616

1717
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
18+
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
19+
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
1820

1921
/**
2022
* @title BondingManager
2123
* @notice Manages bonding, transcoder and rewards/fee accounting related operations of the Livepeer protocol
2224
*/
23-
contract BondingManager is ManagerProxyTarget, IBondingManager {
25+
contract BondingManager is ManagerProxyTarget, IBondingManager, EIP712 {
2426
using SafeMath for uint256;
2527
using SortedDoublyLL for SortedDoublyLL.Data;
2628
using EarningsPool for EarningsPool.Data;
2729
using EarningsPoolLIP36 for EarningsPool.Data;
30+
using ECDSA for bytes32;
31+
32+
bytes32 private constant REWARD_CALLER_TYPEHASH =
33+
keccak256("RewardCallerApproval(address rewardCaller,address transcoder)");
2834

2935
// Constants
3036
// Occurances are replaced at compile time
@@ -148,7 +154,7 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
148154
* - setNumActiveTranscoders()
149155
* @param _controller Address of Controller that this contract will be registered with
150156
*/
151-
constructor(address _controller) Manager(_controller) {}
157+
constructor(address _controller) Manager(_controller) EIP712("BondingManager", "1") {}
152158

153159
/**
154160
* @notice Set unbonding period. Only callable by Controller owner
@@ -194,9 +200,14 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
194200
/**
195201
* @notice Set a reward caller for a transcoder
196202
* @param _rewardCaller Address of the new reward caller
203+
* @param _sig Signature from _rewardCaller approving msg.sender
197204
*/
198-
function setRewardCaller(address _rewardCaller) external whenSystemNotPaused {
205+
function setRewardCaller(address _rewardCaller, bytes calldata _sig) external whenSystemNotPaused {
199206
require(rewardCallerToTranscoder[_rewardCaller] == address(0), "reward caller is already set");
207+
bytes32 structHash = keccak256(abi.encode(REWARD_CALLER_TYPEHASH, _rewardCaller, msg.sender));
208+
bytes32 digest = _hashTypedDataV4(structHash);
209+
address signer = ECDSA.recover(digest, _sig);
210+
require(signer == _rewardCaller, "invalid reward caller signature");
200211
rewardCallerToTranscoder[_rewardCaller] = msg.sender;
201212
emit RewardCallerSet(msg.sender, _rewardCaller);
202213
}
@@ -211,6 +222,7 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
211222
emit RewardCallerUnset(msg.sender, _rewardCaller);
212223
}
213224

225+
214226
/**
215227
* @notice Sets commission rates as a transcoder and if the caller is not in the transcoder pool tries to add it
216228
* @dev Percentages are represented as numerators of fractions over MathUtils.PERC_DIVISOR

test/unit/BondingManager.js

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6107,37 +6107,72 @@ describe("BondingManager", () => {
61076107

61086108
describe("reward delegation", () => {
61096109
const transcoderRewards = 1000
6110+
let chainId
6111+
6112+
const getRewardCallerSignature = async (
6113+
signer,
6114+
rewardCallerAddress,
6115+
transcoderAddress
6116+
) => {
6117+
const domain = {
6118+
name: "BondingManager",
6119+
version: "1",
6120+
chainId,
6121+
verifyingContract: bondingManager.address
6122+
}
6123+
const types = {
6124+
RewardCallerApproval: [
6125+
{name: "rewardCaller", type: "address"},
6126+
{name: "transcoder", type: "address"}
6127+
]
6128+
}
6129+
const value = {
6130+
rewardCaller: rewardCallerAddress,
6131+
transcoder: transcoderAddress
6132+
}
61106133

6111-
it("should only allow active transcoders to set a RewardCaller", async () => {
6112-
const registeredTranscoder = signers[2]
6113-
await bondingManager
6114-
.connect(registeredTranscoder)
6115-
.bond(1000, registeredTranscoder.address)
6116-
await bondingManager
6117-
.connect(registeredTranscoder)
6118-
.transcoder(5, 10)
6134+
return signer._signTypedData(domain, types, value)
6135+
}
61196136

6120-
const registeredTranscoderTx = bondingManager
6121-
.connect(registeredTranscoder)
6122-
.setRewardCaller(nonTranscoder.address)
6137+
before(async () => {
6138+
chainId = (await ethers.provider.getNetwork()).chainId
6139+
})
61236140

6124-
await expect(registeredTranscoderTx).to.be.revertedWith(
6125-
"caller must be an active transcoder"
6141+
it("should require reward caller signature to set a RewardCaller", async () => {
6142+
const badSignature = await getRewardCallerSignature(
6143+
transcoder,
6144+
nonTranscoder.address,
6145+
transcoder.address
61266146
)
6147+
await expect(
6148+
bondingManager
6149+
.connect(transcoder)
6150+
.setRewardCaller(nonTranscoder.address, badSignature)
6151+
).to.be.revertedWith("invalid reward caller signature")
61276152

6153+
const signature = await getRewardCallerSignature(
6154+
nonTranscoder,
6155+
nonTranscoder.address,
6156+
transcoder.address
6157+
)
61286158
const activeTranscoderTx = bondingManager
61296159
.connect(transcoder)
6130-
.setRewardCaller(nonTranscoder.address)
6160+
.setRewardCaller(nonTranscoder.address, signature)
61316161

61326162
await expect(activeTranscoderTx)
61336163
.to.emit(bondingManager, "RewardCallerSet")
61346164
.withArgs(transcoder.address, nonTranscoder.address)
61356165
})
61366166

61376167
it("should allow a transcoder to call reward even if RewardCaller is set", async () => {
6168+
const signature = await getRewardCallerSignature(
6169+
nonTranscoder,
6170+
nonTranscoder.address,
6171+
transcoder.address
6172+
)
61386173
const setRewardCallerTx = bondingManager
61396174
.connect(transcoder)
6140-
.setRewardCaller(nonTranscoder.address)
6175+
.setRewardCaller(nonTranscoder.address, signature)
61416176
await expect(setRewardCallerTx)
61426177
.to.emit(bondingManager, "RewardCallerSet")
61436178
.withArgs(transcoder.address, nonTranscoder.address)
@@ -6166,9 +6201,14 @@ describe("BondingManager", () => {
61666201
})
61676202

61686203
it("should allow a RewardCaller to call reward", async () => {
6204+
const signature = await getRewardCallerSignature(
6205+
nonTranscoder,
6206+
nonTranscoder.address,
6207+
transcoder.address
6208+
)
61696209
const setRewardCallerTx = bondingManager
61706210
.connect(transcoder)
6171-
.setRewardCaller(nonTranscoder.address)
6211+
.setRewardCaller(nonTranscoder.address, signature)
61726212
await expect(setRewardCallerTx)
61736213
.to.emit(bondingManager, "RewardCallerSet")
61746214
.withArgs(transcoder.address, nonTranscoder.address)
@@ -6197,25 +6237,35 @@ describe("BondingManager", () => {
61976237
})
61986238

61996239
it("impossible to set the same RewardCaller twice", async () => {
6240+
const signature = await getRewardCallerSignature(
6241+
nonTranscoder,
6242+
nonTranscoder.address,
6243+
transcoder.address
6244+
)
62006245
const setRewardCallerTx = bondingManager
62016246
.connect(transcoder)
6202-
.setRewardCaller(nonTranscoder.address)
6247+
.setRewardCaller(nonTranscoder.address, signature)
62036248
await expect(setRewardCallerTx)
62046249
.to.emit(bondingManager, "RewardCallerSet")
62056250
.withArgs(transcoder.address, nonTranscoder.address)
62066251

62076252
const setRewardCallerTx2 = bondingManager
62086253
.connect(transcoder)
6209-
.setRewardCaller(nonTranscoder.address)
6254+
.setRewardCaller(nonTranscoder.address, signature)
62106255
await expect(setRewardCallerTx2).to.be.revertedWith(
62116256
"reward caller is already set"
62126257
)
62136258
})
62146259

62156260
it("impossible to unset the RewardCaller for another transcoder", async () => {
6261+
const signature = await getRewardCallerSignature(
6262+
nonTranscoder,
6263+
nonTranscoder.address,
6264+
transcoder.address
6265+
)
62166266
const setRewardCallerTx = bondingManager
62176267
.connect(transcoder)
6218-
.setRewardCaller(nonTranscoder.address)
6268+
.setRewardCaller(nonTranscoder.address, signature)
62196269
await expect(setRewardCallerTx)
62206270
.to.emit(bondingManager, "RewardCallerSet")
62216271
.withArgs(transcoder.address, nonTranscoder.address)
@@ -6229,9 +6279,14 @@ describe("BondingManager", () => {
62296279
})
62306280

62316281
it("should always checkpoint the reward recipient, not the RewardCaller", async () => {
6282+
const signature = await getRewardCallerSignature(
6283+
nonTranscoder,
6284+
nonTranscoder.address,
6285+
transcoder.address
6286+
)
62326287
await bondingManager
62336288
.connect(transcoder)
6234-
.setRewardCaller(nonTranscoder.address)
6289+
.setRewardCaller(nonTranscoder.address, signature)
62356290

62366291
const rewardCallerTx = await bondingManager
62376292
.connect(nonTranscoder)

0 commit comments

Comments
 (0)