-
Notifications
You must be signed in to change notification settings - Fork 4
/
MerkleFunder.sol
182 lines (175 loc) · 7.9 KB
/
MerkleFunder.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import "@api3/airnode-protocol-v1/contracts/utils/ExtendedSelfMulticall.sol";
import "./interfaces/IMerkleFunder.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/utils/Create2.sol";
import "./MerkleFunderDepository.sol";
/// @title Contract that can be called to deploy MerkleFunderDepository
/// contracts or transfer the funds in them within the limitations specified by
/// the respective Merkle trees
/// @notice Use-cases such as self-funded data feeds require users to keep
/// multiple accounts funded. The only way to achieve this without relying on
/// on-chain activity is running a bot that triggers the funding using a hot
/// wallet. In the naive implementation, the funds to be used would also be
/// kept by this hot wallet, which is obviously risky. This contract allows one
/// to deploy a MerkleFunderDepository where they can keep the funds, which
/// this contract only allows to be transferred within the limitations
/// specified by the respective Merkle tree. This means the bot's hot wallet no
/// longer needs to be trusted with the funds, and multiple bots with different
/// hot wallets can be run against the same MerkleFunderDepository deployment
/// for redundancy.
/// @dev MerkleFunder inherits SelfMulticall to allow `fund()` to be
/// multi-called so that multiple fundings can be executed in a single
/// transaction without depending on an external contract. Furthermore, it
/// inherits ExtendedSelfMulticall to allow `getBlockNumber()` be multi-called
/// to avoid race conditions that would have caused the bot implementation to
/// make redundant transactions that revert.
contract MerkleFunder is ExtendedSelfMulticall, IMerkleFunder {
/// @notice Returns the address of the MerkleFunderDepository deployed for
/// the owner address and the Merkle tree root, and zero-address if such a
/// MerkleFunderDepository is not deployed yet
/// @dev The MerkleFunderDepository address can be derived from the owner
/// address and the Merkle tree root using
/// `computeMerkleFunderDepositoryAddress()`, yet doing so is more
/// expensive than reading it from this mapping, which is why we prefer
/// storing it during deployment
mapping(address => mapping(bytes32 => address payable))
public
override ownerToRootToMerkleFunderDepositoryAddress;
/// @notice Called to deterministically deploy the MerkleFunderDepository
/// with the owner address and the Merkle tree root
/// @dev The owner address is allowed to be zero in case the deployer wants
/// to disallow `withdraw()` from being called for the respective
/// MerkleFunderDepository.
/// See `fund()` for how the Merkle tree leaves are derived and how the
/// comprising parameters are validated.
/// @param owner Owner address
/// @param root Merkle tree root
/// @return merkleFunderDepository MerkleFunderDepository address
function deployMerkleFunderDepository(
address owner,
bytes32 root
) external override returns (address payable merkleFunderDepository) {
if (root == bytes32(0)) revert RootZero();
merkleFunderDepository = payable(
new MerkleFunderDepository{salt: bytes32(0)}(owner, root)
);
ownerToRootToMerkleFunderDepositoryAddress[owner][
root
] = merkleFunderDepository;
emit DeployedMerkleFunderDepository(
merkleFunderDepository,
owner,
root
);
}
/// @notice Called to transfer funds from a MerkleFunderDepository to the
/// recipient within the limitations specified by the respective Merkle
/// tree
/// @param owner Owner address
/// @param root Merkle tree root
/// @param proof Merkle tree proof
/// @param recipient Recipient address
/// @param lowThreshold Low hysteresis threshold
/// @param highThreshold High hysteresis threshold
/// @return amount Amount used in funding
function fund(
address owner,
bytes32 root,
bytes32[] calldata proof,
address recipient,
uint256 lowThreshold,
uint256 highThreshold
) external override returns (uint256 amount) {
if (recipient == address(0)) revert RecipientAddressZero();
if (lowThreshold > highThreshold) revert LowThresholdHigherThanHigh();
if (highThreshold == 0) revert HighThresholdZero();
bytes32 leaf = keccak256(
bytes.concat(
keccak256(abi.encode(recipient, lowThreshold, highThreshold))
)
);
if (!MerkleProof.verify(proof, root, leaf)) revert InvalidProof();
uint256 recipientBalance = recipient.balance;
if (recipientBalance > lowThreshold)
revert RecipientBalanceLargerThanLowThreshold();
address payable merkleFunderDepository = ownerToRootToMerkleFunderDepositoryAddress[
owner
][root];
if (merkleFunderDepository == address(0))
revert NoSuchMerkleFunderDepository();
uint256 amountNeededToTopUp;
unchecked {
amountNeededToTopUp = highThreshold - recipientBalance;
}
amount = amountNeededToTopUp <= merkleFunderDepository.balance
? amountNeededToTopUp
: merkleFunderDepository.balance;
if (amount == 0) revert AmountZero();
MerkleFunderDepository(merkleFunderDepository).transfer(
recipient,
amount
);
emit Funded(merkleFunderDepository, recipient, amount);
}
/// @notice Called by the owner of the respective MerkleFunderDepository to
/// withdraw funds in a way that is exempt from the limitations specified
/// by the respective Merkle tree
/// @param root Merkle tree root
/// @param recipient Recipient address
/// @param amount Withdrawal amount
function withdraw(
bytes32 root,
address recipient,
uint256 amount
) public override {
if (recipient == address(0)) revert RecipientAddressZero();
if (amount == 0) revert AmountZero();
address payable merkleFunderDepository = ownerToRootToMerkleFunderDepositoryAddress[
msg.sender
][root];
if (merkleFunderDepository == address(0))
revert NoSuchMerkleFunderDepository();
if (merkleFunderDepository.balance < amount)
revert InsufficientBalance();
MerkleFunderDepository(merkleFunderDepository).transfer(
recipient,
amount
);
emit Withdrew(merkleFunderDepository, recipient, amount);
}
/// @notice Called by the owner of the respective MerkleFunderDepository to
/// withdraw its entire balance in a way that is exempt from the
/// limitations specified by the respective Merkle tree
/// @param root Merkle tree root
/// @param recipient Recipient address
/// @return amount Withdrawal amount
function withdrawAll(
bytes32 root,
address recipient
) external override returns (uint256 amount) {
amount = ownerToRootToMerkleFunderDepositoryAddress[msg.sender][root]
.balance;
withdraw(root, recipient, amount);
}
/// @notice Computes the address of the MerkleFunderDepository
/// @param owner Owner address
/// @param root Merkle tree root
/// @return merkleFunderDepository MerkleFunderDepository address
function computeMerkleFunderDepositoryAddress(
address owner,
bytes32 root
) external view override returns (address merkleFunderDepository) {
if (root == bytes32(0)) revert RootZero();
merkleFunderDepository = Create2.computeAddress(
bytes32(0),
keccak256(
abi.encodePacked(
type(MerkleFunderDepository).creationCode,
abi.encode(owner, root)
)
)
);
}
}