-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add new CheckSecrets dripcheck
- Loading branch information
1 parent
d278707
commit 6841ee3
Showing
4 changed files
with
341 additions
and
0 deletions.
There are no files selected for viewing
102 changes: 102 additions & 0 deletions
102
packages/contracts-bedrock/snapshots/abi/CheckSecrets.json
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,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" | ||
} | ||
] |
9 changes: 9 additions & 0 deletions
9
packages/contracts-bedrock/snapshots/storageLayout/CheckSecrets.json
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,9 @@ | ||
[ | ||
{ | ||
"bytes": "32", | ||
"label": "revealedSecrets", | ||
"offset": 0, | ||
"slot": "0", | ||
"type": "mapping(bytes32 => uint256)" | ||
} | ||
] |
47 changes: 47 additions & 0 deletions
47
packages/contracts-bedrock/src/periphery/drippie/dripchecks/CheckSecrets.sol
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,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); | ||
} | ||
} |
183 changes: 183 additions & 0 deletions
183
packages/contracts-bedrock/test/periphery/drippie/dripchecks/CheckSecrets.t.sol
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,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); | ||
} | ||
} |