-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #272 from OffchainLabs/devjam-henry-child-insurance
parent and child insurance stubs
- Loading branch information
Showing
2 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.0; | ||
|
||
import {AddressAliasHelper} from "../libraries/AddressAliasHelper.sol"; | ||
|
||
// assumes parent and child chain uses ETH for fees | ||
// EVERY FUNCTION MUST NEVER REVERT WHEN CALLED BY PARENT IN SEQUENCE, OTHERWISE QUEUE IS STUCK | ||
contract Child { | ||
address public immutable parentChainAddr; | ||
address public immutable sequencerAddr; | ||
uint256 public sequenceNumber; | ||
uint256 public amtSatisfied; | ||
|
||
constructor(address _parentChainAddr, address _sequencerAddr) { | ||
parentChainAddr = _parentChainAddr; | ||
sequencerAddr = _sequencerAddr; | ||
} | ||
|
||
// gates every function | ||
modifier onlyInSequenceFromParent(uint256 seqNum) { | ||
require(seqNum == sequenceNumber, "invalid sequence number"); | ||
require( | ||
msg.sender == AddressAliasHelper.applyL1ToL2Alias(parentChainAddr), | ||
"only parent chain contract can call" | ||
); | ||
sequenceNumber++; | ||
_; | ||
} | ||
|
||
function deposit(uint256 seqNum) public payable onlyInSequenceFromParent(seqNum) { | ||
// no need to do anything here, just receiving ETH | ||
} | ||
|
||
function withdraw(uint256 seqNum, uint256 amount) public onlyInSequenceFromParent(seqNum) { | ||
amount = amount < address(this).balance ? amount : address(this).balance; | ||
(bool success,) = payable(sequencerAddr).call{value: amount}(""); | ||
require(success, "withdraw failed"); | ||
} | ||
|
||
// If (blocknum, blockhash) is in the history of Chain X, then add amount to S. Otherwise pay out amount to beneficiaryAddr. | ||
// will revert if arb block num < blockNum | ||
function commit( | ||
uint256 seqNum, | ||
address beneficiary, | ||
uint256 amount, | ||
uint256 blockNum, | ||
bytes32 blockHash | ||
) external payable onlyInSequenceFromParent(seqNum) { | ||
// revert if arb block num < blockNum | ||
// pay msg.value to sequencer | ||
// check if blockHash is part of history | ||
// if yes: increment amtSatisfied (S) | ||
// if no: pay out amount to beneficiary. do not revert on failure, because a contract could DoS | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.13; | ||
|
||
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; | ||
import {IInbox} from "../bridge/IInbox.sol"; | ||
import {Child} from "./Child.sol"; | ||
|
||
// assumes parent and child chain uses ETH for fees | ||
contract Parent is EIP712 { | ||
struct SequencerCommitment { | ||
// should be unique | ||
uint256 nonce; | ||
// commitments are unique to a beneficiary. specified here | ||
address beneficiary; | ||
// amount of insurance to sell | ||
uint256 insuranceAmount; | ||
// cost of insurance, paid by the buyer | ||
uint256 insuranceCost; | ||
// block number the sequencer is committing to | ||
uint256 blockNum; | ||
// block hash the sequencer is committing to | ||
bytes32 blockHash; | ||
} | ||
|
||
struct RetryableParams { | ||
uint256 maxSubmissionCost; | ||
uint256 gasLimit; | ||
uint256 gasPrice; | ||
} | ||
|
||
IInbox public immutable inbox; | ||
|
||
address public immutable sequencer; | ||
|
||
address public immutable childChainContract; | ||
|
||
// D | ||
uint256 public depositedAmount; | ||
// I - insurance sold | ||
uint256 public insuranceSold; | ||
This comment has been minimized.
Sorry, something went wrong. |
||
|
||
// sequence number sigma | ||
uint256 public sequenceNumber; | ||
|
||
mapping(bytes32 => bool) public usedCommitments; | ||
|
||
constructor(address _sequencer, IInbox _inbox, address _childChainContract) EIP712("Parent", "1") { | ||
sequencer = _sequencer; | ||
inbox = _inbox; | ||
childChainContract = _childChainContract; | ||
} | ||
|
||
modifier onlySequencer() { | ||
require(msg.sender == sequencer, "only sequencer can call"); | ||
_; | ||
} | ||
|
||
function deposit() public payable onlySequencer { | ||
// increment depositedAmount by value | ||
// create a retryable to hit the child contract deposit function | ||
// increment seqNum | ||
} | ||
|
||
function withdraw(uint256 amount) public onlySequencer { | ||
// decrement depositedAmount by amount | ||
// create a retryable to hit the child contract withdraw function | ||
// increment seqNum | ||
} | ||
|
||
// todo: amount and beneficiary should be part of the commitment | ||
// it's possible to frontrun the buyer and burn the commitment they got from the sequencer. | ||
// This has little cost to the victim, but is annoying since they'll have to go get another commitment and might pay some unnecessary gas. | ||
function buy( | ||
uint256 minSatisfied, | ||
SequencerCommitment calldata commitment, | ||
bytes calldata signature, | ||
RetryableParams calldata retryableParams | ||
) public payable { | ||
// require depositAmount - insuranceSold + minSatisfied >= insuranceAmount | ||
// put this check first because it's the mosy likely to fail without user error | ||
require(depositedAmount - insuranceSold + minSatisfied >= commitment.insuranceAmount, "potential undercollateralization"); | ||
|
||
// verify the signature | ||
require(isValidSignature(commitment, signature), "invalid signature"); | ||
|
||
// require msg.value == insuranceCost + retryableCost | ||
uint256 retryableCost = retryableParams.maxSubmissionCost + retryableParams.gasLimit * retryableParams.gasPrice; | ||
require(msg.value == retryableCost + commitment.insuranceCost, "invalid value"); | ||
|
||
// require !hasUsedCommitment(commitment) | ||
require(!hasUsedCommitment(commitment), "commitment already used"); | ||
|
||
// increment insuranceSold by amount | ||
insuranceSold += commitment.insuranceAmount; | ||
|
||
// mark commitment as used | ||
usedCommitments[hashCommitment(commitment)] = true; | ||
|
||
// build calldata for child contract | ||
bytes memory data = abi.encodeCall( | ||
Child.commit, | ||
( | ||
sequenceNumber, | ||
commitment.beneficiary, | ||
commitment.insuranceAmount, | ||
commitment.blockNum, | ||
commitment.blockHash | ||
) | ||
); | ||
|
||
// create a retryable to hit the child contract settle function, sending msg.value | ||
inbox.createRetryableTicket{value: msg.value}({ | ||
to: childChainContract, | ||
l2CallValue: commitment.insuranceCost, | ||
maxSubmissionCost: retryableParams.maxSubmissionCost, | ||
excessFeeRefundAddress: msg.sender, | ||
callValueRefundAddress: msg.sender, | ||
gasLimit: retryableParams.gasLimit, | ||
maxFeePerGas: retryableParams.gasPrice, | ||
data: data | ||
}); | ||
|
||
// increment seqNum | ||
sequenceNumber++; | ||
} | ||
|
||
function isValidSignature(SequencerCommitment calldata commitment, bytes calldata signature) | ||
public | ||
view | ||
returns (bool) | ||
{ | ||
bytes32 digest = _hashTypedDataV4( | ||
keccak256( | ||
abi.encode( | ||
keccak256( | ||
"SequencerCommitment(uint256 nonce,uint256 blockNum,bytes32 blockHash,uint256 pricePerEthWad)" | ||
), | ||
commitment | ||
) | ||
) | ||
); | ||
address signer = ECDSA.recover(digest, signature); | ||
return signer == sequencer; | ||
} | ||
|
||
function hasUsedCommitment(SequencerCommitment calldata commitment) public view returns (bool) { | ||
return usedCommitments[hashCommitment(commitment)]; | ||
} | ||
|
||
function hashCommitment(SequencerCommitment calldata commitment) public pure returns (bytes32) { | ||
return keccak256(abi.encode(commitment)); | ||
} | ||
} |
Both in the code for the contracts and in the names of the directories, we need to change "Insurance" to "Bonding" or "Bonds" or something like that. This is a request from legal.