/
FranchiserFactory.sol
165 lines (148 loc) · 5.15 KB
/
FranchiserFactory.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.15;
import {IFranchiserFactory} from "./interfaces/FranchiserFactory/IFranchiserFactory.sol";
import {FranchiserImmutableState} from "./base/FranchiserImmutableState.sol";
import {Address} from "openzeppelin-contracts/contracts/utils/Address.sol";
import {Clones} from "openzeppelin-contracts/contracts/proxy/Clones.sol";
import {SafeTransferLib, ERC20} from "solmate/utils/SafeTransferLib.sol";
import {IVotingToken} from "./interfaces/IVotingToken.sol";
import {Franchiser} from "./Franchiser.sol";
contract FranchiserFactory is IFranchiserFactory, FranchiserImmutableState {
using Address for address;
using Clones for address;
using SafeTransferLib for ERC20;
/// @inheritdoc IFranchiserFactory
uint96 public constant INITIAL_MAXIMUM_SUBDELEGATEES = 2**3; // 8
/// @inheritdoc IFranchiserFactory
Franchiser public immutable franchiserImplementation;
constructor(IVotingToken votingToken_)
FranchiserImmutableState(votingToken_)
{
franchiserImplementation = new Franchiser(votingToken_);
}
function getSalt(address owner, address delegatee)
private
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(owner, delegatee));
}
/// @inheritdoc IFranchiserFactory
function getFranchiser(address owner, address delegatee)
public
view
returns (Franchiser)
{
return
Franchiser(
address(franchiserImplementation).predictDeterministicAddress(
getSalt(owner, delegatee),
address(this)
)
);
}
/// @inheritdoc IFranchiserFactory
function fund(address delegatee, uint256 amount)
public
returns (Franchiser franchiser)
{
franchiser = getFranchiser(msg.sender, delegatee);
if (!address(franchiser).isContract()) {
// deploy a new contract if necessary
address(franchiserImplementation).cloneDeterministic(
getSalt(msg.sender, delegatee)
);
franchiser.initialize(
msg.sender,
delegatee,
INITIAL_MAXIMUM_SUBDELEGATEES
);
}
ERC20(address(votingToken)).safeTransferFrom(
msg.sender,
address(franchiser),
amount
);
}
/// @inheritdoc IFranchiserFactory
function fundMany(address[] calldata delegatees, uint256[] calldata amounts)
external
returns (Franchiser[] memory franchisers)
{
if (delegatees.length != amounts.length)
revert ArrayLengthMismatch(delegatees.length, amounts.length);
franchisers = new Franchiser[](delegatees.length);
unchecked {
for (uint256 i = 0; i < delegatees.length; i++)
franchisers[i] = fund(delegatees[i], amounts[i]);
}
}
/// @inheritdoc IFranchiserFactory
function recall(address delegatee, address to) public {
Franchiser franchiser = getFranchiser(msg.sender, delegatee);
if (address(franchiser).isContract()) franchiser.recall(to);
}
/// @inheritdoc IFranchiserFactory
function recallMany(address[] calldata delegatees, address[] calldata tos)
external
{
if (delegatees.length != tos.length)
revert ArrayLengthMismatch(delegatees.length, tos.length);
unchecked {
for (uint256 i = 0; i < delegatees.length; i++)
recall(delegatees[i], tos[i]);
}
}
function permit(
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) private {
// this check ensures that if the permit is front-run,
// the call does not fail
if (votingToken.allowance(msg.sender, address(this)) < amount)
votingToken.permit(
msg.sender,
address(this),
amount,
deadline,
v,
r,
s
);
}
/// @inheritdoc IFranchiserFactory
function permitAndFund(
address delegatee,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (Franchiser) {
permit(amount, deadline, v, r, s);
return fund(delegatee, amount);
}
/// @inheritdoc IFranchiserFactory
function permitAndFundMany(
address[] calldata delegatees,
uint256[] calldata amounts,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (Franchiser[] memory franchisers) {
if (delegatees.length != amounts.length)
revert ArrayLengthMismatch(delegatees.length, amounts.length);
uint256 amount = 0;
for (uint256 i = 0; i < delegatees.length; i++) amount += amounts[i];
permit(amount, deadline, v, r, s);
franchisers = new Franchiser[](delegatees.length);
unchecked {
for (uint256 i = 0; i < delegatees.length; i++)
franchisers[i] = fund(delegatees[i], amounts[i]);
}
}
}