teleportr: add TeleportrDeposit and TeleportrDisburser contracts#2145
teleportr: add TeleportrDeposit and TeleportrDisburser contracts#2145mslipper merged 12 commits intoethereum-optimism:developfrom
Conversation
|
| receive() | ||
| external | ||
| payable | ||
| isLowerThanMaxDepositAmount |
There was a problem hiding this comment.
It feels like a bit of an over optimization to have this many modifiers and composing them all together, perhaps we can move the functionality directly into the function itself or combine them into a single modifier?
There was a problem hiding this comment.
Agree, esp. since they are only used once, it's more readable to inline the checks.
|
Link to original contract for those who are interested: https://github.com/0xclem/teleportr/blob/main/contracts/BridgeDeposit.sol |
| { | ||
| uint256 _depositId = totalDeposits + 1; | ||
| emit EtherReceived(_depositId, msg.sender, msg.value); | ||
| totalDeposits = _depositId; | ||
| } |
There was a problem hiding this comment.
The following is cleaner IMO
| { | |
| uint256 _depositId = totalDeposits + 1; | |
| emit EtherReceived(_depositId, msg.sender, msg.value); | |
| totalDeposits = _depositId; | |
| } | |
| { | |
| totalDeposits += 1; | |
| emit EtherReceived(totalDeposits, msg.sender, msg.value); | |
| } |
There was a problem hiding this comment.
Sounds good, changed. For my own curiosity, does this approach not use an extra SLOAD?
There was a problem hiding this comment.
If we were using foundry it would be really easy to take a gas snapshot and see, would need to look at the execution trace or bytecode to know for sure
There was a problem hiding this comment.
I'd support using foundry if we put this in it's own package (which I think we should do anyways).
There was a problem hiding this comment.
foundry works in this package with forge test --hardhat
| modifier onlyOwner() { | ||
| require(msg.sender == owner, "only accessible to owner"); | ||
| _; | ||
| } |
There was a problem hiding this comment.
Also needs setOwner() and getOwner() methods like the other contract.
There was a problem hiding this comment.
made owner public and added setOwner 👍
| emit OwnerSet(address(0), msg.sender); | ||
| emit MaxDepositAmountSet(0, _maxDepositAmount); | ||
| emit MinDepositAmountSet(0, _minDepositAmount); | ||
| emit MaxBalanceSet(0, _maxBalance); | ||
| emit CanReceiveDepositSet(_canReceiveDeposit); |
There was a problem hiding this comment.
Do we really need all these events? Is anyone actually watching for them?
There was a problem hiding this comment.
It is a lot of events but it does follow best practices re: allowing an offchain actor to observe all events and recompute the state of the contract
There was a problem hiding this comment.
I removed the events, but if we want to add them back as per @tynes comment I can do that
There was a problem hiding this comment.
Honestly I think @tynes makes a good point.
At least we can remove CanReceiveDepositSet now.
There was a problem hiding this comment.
This contract has a backdoor in it where all the money can be drained so we should make it transparent in its operations. We should definitely have an event for withdrawBalance - it could help with ensuring security of the key we are using
| uint256 public totalDisbursements; | ||
|
|
||
| constructor() { | ||
| owner = msg.sender; |
There was a problem hiding this comment.
We probably don't want the actual owner to be the deployer key. If we add a means of transferring ownership, then this might be OK.
Same comment goes for the other constructor.
There was a problem hiding this comment.
Sounds good, I will check out how our other contracts do this today
packages/contracts/contracts/L2/teleportr/TeleportrDisbursement.sol
Outdated
Show resolved
Hide resolved
packages/contracts/contracts/L2/teleportr/TeleportrDisbursement.sol
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
See inline comments, some are blocking.
Additional considerations:
- Auth: I think we need to properly spec how we want to handle authorization. The approach here (
onlyOwner) is different from most of our other contracts which use the AddressManager contract. That might be fine, but we should think about how we want to handle it. - Upgradability: Do we need it here? Probably not, we can just replace the contracts.
- Gas to forward: The description asks some good questions:
What` is the appropriate gas limit to set on the each disbursement receiver's call?
2300 seems like the default/standard, I'll ask around if that's changed.
Should it be adjustable by the owner?
IMO no, if it's not enough we can just replace the contract, because we want to minimize SLOADs on that path.
- Organization: I think there's a good argument to be made that this could be in a separate package. It has no deps with the current contract package.
- Testing, deployment scripts, and NatSpec comments We should have some of those IMO.
|
Regarding upgradability, I don't think we want to place this behind a proxy to save the sload + delegatecall but I do think its a good idea to put an entry in the address manager for this contract so that offchain actors can always first look up the address of the teleporter contract. Or we could name it using ens |
bbd27c4 to
ea9f9f0
Compare
5e6b4f7 to
faee154
Compare
| event EtherReceived( | ||
| uint256 indexed depositId, | ||
| address indexed emitter, | ||
| uint256 amount |
There was a problem hiding this comment.
We can save about 150 gas by adding the indexed kw to this last arg (source).
| modifier isOwner() { | ||
| require(msg.sender == libAddressManager.owner(), "Caller is not owner"); | ||
| _; | ||
| } |
There was a problem hiding this comment.
Note that libAddressManager.owner() is a very sensitive address that we should only access for upgrades or occasionally modifying parameters.
I'd suggest:
- use the
isOwnermodifier on the setter functions - create a different role (
withdrawer), modifier (isWithdrawer) and setter function (setWithdrawer() isOwner) for thewithdrawBalance()call. The address for this role can then be used more often in a hot wallet (as I assume we'll want to drain this contract on a semi-regular basis).
| address _owner = libAddressManager.owner(); | ||
| uint256 _balance = address(this).balance; | ||
| emit BalanceWithdrawn(_owner, _balance); | ||
| payable(_owner).transfer(_balance); |
There was a problem hiding this comment.
Further to the comment on the isOwner() modifier above, we may or may not want to send withdrawals payments to this address.
a8db6be to
c458879
Compare
This is a direct copy of the BridgeDeposit.sol contract apart from the name change, some code moves to make the linter happy, and promoting various public methods to external to satisfy slither.
c458879 to
42c9be9
Compare
Codecov Report
@@ Coverage Diff @@
## develop #2145 +/- ##
===========================================
- Coverage 73.35% 73.04% -0.31%
===========================================
Files 84 86 +2
Lines 2770 2846 +76
Branches 474 486 +12
===========================================
+ Hits 2032 2079 +47
- Misses 738 767 +29
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
| require(msg.value >= minDepositAmount, "Deposit amount is too small"); | ||
| require(msg.value <= maxDepositAmount, "Deposit amount is too big"); | ||
| require(address(this).balance <= maxBalance, "Contract max balance exceeded"); |
There was a problem hiding this comment.
Nice, this is much more readable than modifiers IMO.
| minDepositAmount = _minDepositAmount; | ||
| maxDepositAmount = _maxDepositAmount; | ||
| maxBalance = _maxBalance; | ||
| totalDeposits = 0; |
There was a problem hiding this comment.
FYI this is a noop. State vars are initialized to zero:
The concept of “undefined” or “null” values does not exist in Solidity, but newly declared variables always have a default value dependent on its type.
maurelian
left a comment
There was a problem hiding this comment.
LGTM.
Just left a couple comments to inform, but nothing is blocking.
Security considerations taken into account during my review:
-
Possibility of draining funds:
I verified that all functions that modify key state vars useonlyOwnerfor auth. The only public function isreceive(). -
Stuck funds:
It is possible to have funds not paid out in the disbursement function, which would be left in the contract, but there are manual work around for this.
Ref #2136 --------- Co-authored-by: Arun Dhyani <dhyaniarun7@gmail.com>
Description
This PR adds two new contracts,
L1/teleportr/TeleportrDeposit.solandL2/teleportr/TeleportrDisburser.sol, that will form the core of the newteleportrservice. TheTeleportrDepositcontract is modeled off the existingBridgeDeposit.solcontract, but adds the ability to set aminDepositAmountand emit adepositIdfor eachEtherReceivedevent to uniquely identify them in our postgres instance. TheTeleportrDisbursercontract implements the L2 disbursement side of the bridge, which supports batched disbursements and also tracks the deposit ID's to make implementation of theteleportrdriver simpler.Some questions I have:
canReceivemodified necessary? Or can we just emulate this by settingmaxDepositAmount = 0.call? Should it be adjustable by the owner?I will hold off on updating the docs until we have some rough consensus on the design :)
Metadata