diff --git a/README.md b/README.md index 596670b..fbf5a20 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ leads. The leads can choose to update this value themselves. | Contract | Address | Version | | -------- | ------- | ------- | +| `PactFactory` | [0x0bc342e7bbf737ba123b1167e528a28cebb8b679](https://optimistic.etherscan.io/address/0x0bc342e7bbf737ba123b1167e528a28cebb8b679) | `0.3.0` | +| `Pact` (implementation) | [0x60d1a1755065e4cac8777248c2878c847c9a5dd5](https://optimistic.etherscan.io/address/0x60d1a1755065e4cac8777248c2878c847c9a5dd5) | `0.3.0` | | `PactFactory` | [0x642a7864cBe44ED24D408Cbc38117Cfd6E6D1a95](https://optimistic.etherscan.io/address/0x642a7864cBe44ED24D408Cbc38117Cfd6E6D1a95) | `0.2.0` | | `Pact` (implementation) | [0x4eE4ff6D24c8D334fA41b560Dac95BB3CEF828a1](https://optimistic.etherscan.io/address/0x4eE4ff6D24c8D334fA41b560Dac95BB3CEF828a1) | `0.2.0` | diff --git a/src/Pact.sol b/src/Pact.sol index f4e1324..7e736f9 100644 --- a/src/Pact.sol +++ b/src/Pact.sol @@ -18,7 +18,7 @@ import { SafeCall } from "./SafeCall.sol"; /// It is expected that the pre-agreed upon commitment is observed. The rule of law /// dictates meatspace. contract Pact is Clone { - string constant public version = "0.2.0"; + string constant public version = "0.3.0"; /// @notice Used to determine if the pact has been initialized. bool internal _initialized; @@ -68,6 +68,9 @@ contract Pact is Clone { /// @notice Error when trying to resolve the Pact too early. error Early(); + /// @notice Error when putting too much value into the Pact. + error Overflow(); + /// @notice Error when trying to `initialize()` when the Pact has /// already been initialized. error Initialized(); @@ -90,6 +93,7 @@ contract Pact is Clone { /// so that ether can be returned in case not enough capital is accumulated. fallback() external payable { if (resolved) revert Resolved(); + if (address(this).balance > sum()) revert Overflow(); address sender = msg.sender; uint256 value = msg.value; @@ -119,7 +123,12 @@ contract Pact is Clone { return duration() + start; } - /// @notice + /// @notice Convenience getter for the balance in the Pact. + function balance() public view returns (uint256) { + return address(this).balance; + } + + /// @notice Useful for ensuring that the agreement matches the commitment. function check(string memory _agreement) public pure returns (bool) { return keccak256(bytes(_agreement)) == commitment(); } diff --git a/test/MutualAssuranceContract.t.sol b/test/MutualAssuranceContract.t.sol index d5d6c29..8f6fa8c 100644 --- a/test/MutualAssuranceContract.t.sol +++ b/test/MutualAssuranceContract.t.sol @@ -278,4 +278,28 @@ contract MutualAssuranceContractTest is Test { uint256 alicePostBalance = alice.balance; assertEq(alicePostBalance, alicePreBalance); } + + /// @notice Only allow the Pact to accept the exact amount of ether. This creates a race + /// condition. + function test_pact_overflow() external { + Pact pact = _deploy(); + + uint256 value = pact.sum() - 1; + + vm.expectEmit(true, true, true, true, address(pact)); + emit Assurance(alice, value); + + vm.prank(alice); + (bool success, ) = address(pact).call{ value: value }(hex""); + assertTrue(success); + + vm.expectRevert(abi.encodeWithSelector(Pact.Overflow.selector)); + address(pact).call{ value: 2 }(hex""); + + vm.expectEmit(true, true, true, true, address(pact)); + emit Assurance(alice, 1); + + vm.prank(alice); + (success, ) = address(pact).call{ value: 1 }(hex""); + } }