Skip to content

Commit

Permalink
Merge pull request #272 from OffchainLabs/devjam-henry-child-insurance
Browse files Browse the repository at this point in the history
parent and child insurance stubs
  • Loading branch information
godzillaba authored Dec 4, 2024
2 parents 1de32cb + 0a73ccd commit 0c5b441
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 0 deletions.
55 changes: 55 additions & 0 deletions src/insurance/Child.sol
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
}
}
153 changes: 153 additions & 0 deletions src/insurance/Parent.sol
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.

Copy link
@eljobe

eljobe Dec 9, 2024

Member

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.


// 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));
}
}

0 comments on commit 0c5b441

Please sign in to comment.