Skip to content

Commit

Permalink
feat: add new CheckSecrets dripcheck
Browse files Browse the repository at this point in the history
  • Loading branch information
smartcontracts committed May 20, 2024
1 parent d278707 commit 6841ee3
Show file tree
Hide file tree
Showing 4 changed files with 341 additions and 0 deletions.
102 changes: 102 additions & 0 deletions packages/contracts-bedrock/snapshots/abi/CheckSecrets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
[
{
"inputs": [
{
"internalType": "bytes",
"name": "_params",
"type": "bytes"
}
],
"name": "check",
"outputs": [
{
"internalType": "bool",
"name": "execute_",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_secret",
"type": "bytes"
}
],
"name": "reveal",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "revealedSecrets",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "secretHash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes",
"name": "secret",
"type": "bytes"
}
],
"name": "SecretRevealed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "delay",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "secretHashMustExist",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "secretHashMustNotExist",
"type": "bytes32"
}
],
"indexed": false,
"internalType": "struct CheckSecrets.Params",
"name": "params",
"type": "tuple"
}
],
"name": "_EventToExposeStructInABI__Params",
"type": "event"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"bytes": "32",
"label": "revealedSecrets",
"offset": 0,
"slot": "0",
"type": "mapping(bytes32 => uint256)"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { IDripCheck } from "../IDripCheck.sol";

/// @title CheckSecrets
/// @notice DripCheck that checks if specific secrets exist (or not). Supports having a secret that
/// must exist for the check to pass as well as a second secret that must not exist. First
/// secret can be revealed to begin the drip, second secret can be revealed to stop it.
contract CheckSecrets is IDripCheck {
struct Params {
uint256 delay;
bytes32 secretHashMustExist;
bytes32 secretHashMustNotExist;
}

/// @notice External event used to help client-side tooling encode parameters.
/// @param params Parameters to encode.
event _EventToExposeStructInABI__Params(Params params);

/// @notice Event emitted when a secret is revealed.
event SecretRevealed(bytes32 indexed secretHash, bytes secret);

/// @notice Keeps track of when secrets were revealed.
mapping(bytes32 => uint256) public revealedSecrets;

/// @inheritdoc IDripCheck
function check(bytes memory _params) external view returns (bool execute_) {
Params memory params = abi.decode(_params, (Params));

// Check that the secrets have/have not been revealed.
execute_ = (
revealedSecrets[params.secretHashMustExist] > 0
&& block.timestamp >= revealedSecrets[params.secretHashMustExist] + params.delay
&& revealedSecrets[params.secretHashMustNotExist] == 0
);
}

/// @notice Reveal a secret.
/// @param _secret Secret to reveal.
function reveal(bytes memory _secret) external {
bytes32 secretHash = keccak256(_secret);
require(revealedSecrets[secretHash] == 0, "CheckSecrets: secret already revealed");
revealedSecrets[secretHash] = block.timestamp;
emit SecretRevealed(secretHash, _secret);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { Test } from "forge-std/Test.sol";
import { CheckSecrets } from "src/periphery/drippie/dripchecks/CheckSecrets.sol";

/// @title CheckSecretsTest
contract CheckSecretsTest is Test {
/// @notice Event emitted when a secret is revealed.
event SecretRevealed(bytes32 indexed secretHash, bytes secret);

/// @notice An instance of the CheckSecrets contract.
CheckSecrets c;

/// @notice A secret that must exist.
bytes secretMustExist = bytes(string("secretMustExist"));

/// @notice A secret that must not exist.
bytes secretMustNotExist = bytes(string("secretMustNotExist"));

/// @notice A delay period for the check.
uint256 delay = 100;

/// @notice Deploy the `CheckSecrets` contract.
function setUp() external {
c = new CheckSecrets();
}

/// @notice Test that basic secret revealing works.
function test_reveal_succeeds() external {
// Simple reveal and check assertions.
vm.expectEmit(address(c));
emit SecretRevealed(keccak256(secretMustExist), secretMustExist);
c.reveal(secretMustExist);
assertEq(c.revealedSecrets(keccak256(secretMustExist)), block.timestamp);
}

/// @notice Test that revealing the same secret twice does not work.
function test_reveal_twice_fails() external {
// Reveal the secret once.
uint256 ts = block.timestamp;
c.reveal(secretMustExist);
assertEq(c.revealedSecrets(keccak256(secretMustExist)), ts);

// Forward time and reveal again, should fail, same original timestamp.
vm.warp(ts + 1);
vm.expectRevert("CheckSecrets: secret already revealed");
c.reveal(secretMustExist);
assertEq(c.revealedSecrets(keccak256(secretMustExist)), ts);
}

/// @notice Test that the check function returns true when the first secret is revealed but the
/// second secret is still hidden and the delay period has elapsed when the delay
/// period is non-zero. Here we warp to exactly the delay period.
function test_check_secretRevealedWithDelayEq_succeeds() external {
CheckSecrets.Params memory p = CheckSecrets.Params({
delay: delay,
secretHashMustExist: keccak256(secretMustExist),
secretHashMustNotExist: keccak256(secretMustNotExist)
});

// Reveal the secret that must exist.
c.reveal(secretMustExist);

// Forward time to the delay period.
vm.warp(block.timestamp + delay);

// Beyond the delay, secret revealed, check should succeed.
assertEq(c.check(abi.encode(p)), true);
}

/// @notice Test that the check function returns true when the first secret is revealed but the
/// second secret is still hidden and the delay period has elapsed when the delay
/// period is non-zero. Here we warp to after the delay period.
function test_check_secretRevealedWithDelayGt_succeeds() external {
CheckSecrets.Params memory p = CheckSecrets.Params({
delay: delay,
secretHashMustExist: keccak256(secretMustExist),
secretHashMustNotExist: keccak256(secretMustNotExist)
});

// Reveal the secret that must exist.
c.reveal(secretMustExist);

// Forward time to after the delay period.
vm.warp(block.timestamp + delay + 1);

// Beyond the delay, secret revealed, check should succeed.
assertEq(c.check(abi.encode(p)), true);
}

/// @notice Test that the check function returns true when the first secret is revealed but the
/// second secret is still hidden and the delay period is zero, meaning the reveal can
/// happen in the same block as the execution.
function test_check_secretRevealedZeroDelay_succeeds() external {
CheckSecrets.Params memory p = CheckSecrets.Params({
delay: 0,
secretHashMustExist: keccak256(secretMustExist),
secretHashMustNotExist: keccak256(secretMustNotExist)
});

// Reveal the secret that must exist.
c.reveal(secretMustExist);

// Note we don't need to forward time here.
// Secret revealed, no delay, check should succeed.
assertEq(c.check(abi.encode(p)), true);
}

/// @notice Test that the check function returns false when the first secret is revealed but
/// the delay period has not yet elapsed.
function test_check_secretRevealedBeforeDelay_fails() external {
CheckSecrets.Params memory p = CheckSecrets.Params({
delay: delay,
secretHashMustExist: keccak256(secretMustExist),
secretHashMustNotExist: keccak256(secretMustNotExist)
});

// Reveal the secret that must exist.
c.reveal(secretMustExist);

// Forward time to before the delay period.
vm.warp(block.timestamp + delay - 1);

// Not beyond the delay, check should fail.
assertEq(c.check(abi.encode(p)), false);
}

/// @notice Test that the check function returns false when the first secret is not revealed.
function test_check_secretNotRevealed_fails() external {
CheckSecrets.Params memory p = CheckSecrets.Params({
delay: delay,
secretHashMustExist: keccak256(secretMustExist),
secretHashMustNotExist: keccak256(secretMustNotExist)
});

// Forward beyond the delay period.
vm.warp(block.timestamp + delay + 1);

// Secret not revealed, check should fail.
assertEq(c.check(abi.encode(p)), false);
}

/// @notice Test that the check function returns false when the second secret is revealed.
function test_check_secondSecretRevealed_fails() external {
CheckSecrets.Params memory p = CheckSecrets.Params({
delay: delay,
secretHashMustExist: keccak256(secretMustExist),
secretHashMustNotExist: keccak256(secretMustNotExist)
});

// Reveal the secret that must not exist.
c.reveal(secretMustNotExist);

// Forward beyond the delay period.
vm.warp(block.timestamp + delay + 1);

// Both secrets revealed, check should fail.
assertEq(c.check(abi.encode(p)), false);
}

/// @notice Test that the check function returns false when the second secret is revealed even
/// though the first secret is also revealed.
function test_check_firstAndSecondSecretRevealed_fails() external {
CheckSecrets.Params memory p = CheckSecrets.Params({
delay: delay,
secretHashMustExist: keccak256(secretMustExist),
secretHashMustNotExist: keccak256(secretMustNotExist)
});

// Reveal the secret that must exist.
c.reveal(secretMustExist);

// Reveal the secret that must not exist.
c.reveal(secretMustNotExist);

// Forward beyond the delay period.
vm.warp(block.timestamp + delay + 1);

// Both secrets revealed, check should fail.
assertEq(c.check(abi.encode(p)), false);
}
}

0 comments on commit 6841ee3

Please sign in to comment.