diff --git a/.gitmodules b/.gitmodules index 1fd43b5..693e719 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "modules/ds-test"] - path = modules/ds-test - url = https://github.com/dapphub/ds-test +[submodule "modules/contract-test-utils"] + path = modules/contract-test-utils + url = https://github.com/maple-labs/contract-test-utils diff --git a/contracts/ERC20.sol b/contracts/ERC20.sol index 98ed44e..7c2d5bc 100644 --- a/contracts/ERC20.sol +++ b/contracts/ERC20.sol @@ -40,6 +40,16 @@ contract ERC20 is IERC20 { return true; } + function decreaseAllowance(address spender_, uint256 subtractedAmount_) external override returns (bool success_) { + _approve(msg.sender, spender_, allowance[msg.sender][spender_] - subtractedAmount_); + return true; + } + + function increaseAllowance(address spender_, uint256 addedAmount_) external override returns (bool success_) { + _approve(msg.sender, spender_, allowance[msg.sender][spender_] + addedAmount_); + return true; + } + function transfer(address recipient_, uint256 amount_) external override returns (bool success_) { _transfer(msg.sender, recipient_, amount_); return true; diff --git a/contracts/ERC20Permit.sol b/contracts/ERC20Permit.sol index a0911c1..b3363be 100644 --- a/contracts/ERC20Permit.sol +++ b/contracts/ERC20Permit.sol @@ -69,6 +69,16 @@ contract ERC20Permit is IERC20Permit { return true; } + function decreaseAllowance(address spender_, uint256 subtractedAmount_) external override returns (bool success_) { + _approve(msg.sender, spender_, allowance[msg.sender][spender_] - subtractedAmount_); + return true; + } + + function increaseAllowance(address spender_, uint256 addedAmount_) external override returns (bool success_) { + _approve(msg.sender, spender_, allowance[msg.sender][spender_] + addedAmount_); + return true; + } + function permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external override { require(deadline >= block.timestamp, "ERC20Permit:EXPIRED"); bytes32 digest = keccak256( diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol index 39ba12e..27980a2 100644 --- a/contracts/interfaces/IERC20.sol +++ b/contracts/interfaces/IERC20.sol @@ -5,80 +5,98 @@ pragma solidity ^0.8.7; interface IERC20 { /** - * @dev Emits an event indicating that tokens have moved from one account to another. - * @param owner_ Account that tokens have moved from. - * @param recipient_ Account that tokens have moved to. - * @param amount_ Amount of tokens that have been transferred. + * @dev Emits an event indicating that tokens have moved from one account to another. + * @param owner_ Account that tokens have moved from. + * @param recipient_ Account that tokens have moved to. + * @param amount_ Amount of tokens that have been transferred. */ event Transfer(address indexed owner_, address indexed recipient_, uint256 amount_); /** - * @dev Emits an event indicating that one account has set the allowance of another account over their tokens. - * @param owner_ Account that tokens are approved from. - * @param spender_ Account that tokens are approved for. - * @param amount_ Amount of tokens that have been approved. + * @dev Emits an event indicating that one account has set the allowance of another account over their tokens. + * @param owner_ Account that tokens are approved from. + * @param spender_ Account that tokens are approved for. + * @param amount_ Amount of tokens that have been approved. */ event Approval(address indexed owner_, address indexed spender_, uint256 amount_); /** - * @dev Returns the name of the token. + * @dev Returns the name of the token. */ function name() external view returns (string memory name_); /** - * @dev Returns the symbol of the token. + * @dev Returns the symbol of the token. */ function symbol() external view returns (string memory symbol_); /** - * @dev Returns the decimal precision used by the token. + * @dev Returns the decimal precision used by the token. */ function decimals() external view returns (uint8 decimals_); /** - * @dev Returns the total amount of tokens in existence. + * @dev Returns the total amount of tokens in existence. */ function totalSupply() external view returns (uint256 totalSupply_); /** - * @dev Returns the amount of tokens owned by a given account. - * @param account_ Account that owns the tokens. + * @dev Returns the amount of tokens owned by a given account. + * @param account_ Account that owns the tokens. */ function balanceOf(address account_) external view returns (uint256 balance_); /** - * @dev Function that returns the allowance that one account has given another over their tokens. - * @param owner_ Account that tokens are approved from. - * @param spender_ Account that tokens are approved for. + * @dev Function that returns the allowance that one account has given another over their tokens. + * @param owner_ Account that tokens are approved from. + * @param spender_ Account that tokens are approved for. */ function allowance(address owner_, address spender_) external view returns (uint256 allowance_); /** - * @dev Function that allows one account to set the allowance of another account over their tokens. - * Emits an {Approval} event. - * @param spender_ Account that tokens are approved for. - * @param amount_ Amount of tokens that have been approved. - * @return success_ Boolean indicating whether the operation succeeded. + * @dev Function that allows one account to set the allowance of another account over their tokens. + * Emits an {Approval} event. + * @param spender_ Account that tokens are approved for. + * @param amount_ Amount of tokens that have been approved. + * @return success_ Boolean indicating whether the operation succeeded. */ function approve(address spender_, uint256 amount_) external returns (bool success_); /** - * @dev Moves an amount of tokens from `msg.sender` to a specified account. - * Emits a {Transfer} event. - * @param recipient_ Account that receives tokens. - * @param amount_ Amount of tokens that are transferred. - * @return success_ Boolean indicating whether the operation succeeded. + * @dev Function that allows one account to decrease the allowance of another account over their tokens. + * Emits an {Approval} event. + * @param spender_ Account that tokens are approved for. + * @param subtractedAmount_ Amount to decrease approval by. + * @return success_ Boolean indicating whether the operation succeeded. + */ + function decreaseAllowance(address spender_, uint256 subtractedAmount_) external returns (bool success_); + + /** + * @dev Function that allows one account to increase the allowance of another account over their tokens. + * Emits an {Approval} event. + * @param spender_ Account that tokens are approved for. + * @param addedAmount_ Amount to increase approval by. + * @return success_ Boolean indicating whether the operation succeeded. + */ + function increaseAllowance(address spender_, uint256 addedAmount_) external returns (bool success_); + + /** + * @dev Moves an amount of tokens from `msg.sender` to a specified account. + * Emits a {Transfer} event. + * @param recipient_ Account that receives tokens. + * @param amount_ Amount of tokens that are transferred. + * @return success_ Boolean indicating whether the operation succeeded. */ function transfer(address recipient_, uint256 amount_) external returns (bool success_); /** - * @dev Moves a pre-approved amount of tokens from a sender to a specified account. - * Emits a {Transfer} event. - * Emits an {Approval} event. - * @param owner_ Account that tokens are moving from. - * @param recipient_ Account that receives tokens. - * @param amount_ Amount of tokens that are transferred. - * @return success_ Boolean indicating whether the operation succeeded. + * @dev Moves a pre-approved amount of tokens from a sender to a specified account. + * Emits a {Transfer} event. + * Emits an {Approval} event. + * @param owner_ Account that tokens are moving from. + * @param recipient_ Account that receives tokens. + * @param amount_ Amount of tokens that are transferred. + * @return success_ Boolean indicating whether the operation succeeded. */ function transferFrom(address owner_, address recipient_, uint256 amount_) external returns (bool success_); diff --git a/contracts/interfaces/IERC20Permit.sol b/contracts/interfaces/IERC20Permit.sol index 9763375..424abb3 100644 --- a/contracts/interfaces/IERC20Permit.sol +++ b/contracts/interfaces/IERC20Permit.sol @@ -7,33 +7,33 @@ import { IERC20 } from "./IERC20.sol"; interface IERC20Permit is IERC20 { /** - @dev Approve by signature. - @param owner_ Owner address that signed the permit. - @param spender_ Spender of the permit. - @param amount_ Permit approval spend limit. - @param deadline_ Deadline after which the permit is invalid. - @param v_ ECDSA signature v component. - @param r_ ECDSA signature r component. - @param s_ ECDSA signature s component. + * @dev Approve by signature. + * @param owner_ Owner address that signed the permit. + * @param spender_ Spender of the permit. + * @param amount_ Permit approval spend limit. + * @param deadline_ Deadline after which the permit is invalid. + * @param v_ ECDSA signature v component. + * @param r_ ECDSA signature r component. + * @param s_ ECDSA signature s component. */ function permit(address owner_, address spender_, uint amount_, uint deadline_, uint8 v_, bytes32 r_, bytes32 s_) external; /** - * @dev Returns the permit type hash. - * @return hash_ The typehash for the commit. + * @dev Returns the permit type hash. + * @return hash_ The typehash for the commit. */ function PERMIT_TYPEHASH() external pure returns (bytes32 hash_); /** - * @dev Returns the nonce for the given owner. - * @param owner The addreses of the owner account. - * @return nonce_ The current nonce. + * @dev Returns the nonce for the given owner. + * @param owner The addreses of the owner account. + * @return nonce_ The current nonce. */ function nonces(address owner) external view returns (uint256 nonce_); /** - * @dev Returns the signature domain separator. - * @return domain_ The domain for the contract. + * @dev Returns the signature domain separator. + * @return domain_ The domain for the contract. */ function DOMAIN_SEPARATOR() external view returns (bytes32 domain_); diff --git a/contracts/test/ERC20.t.sol b/contracts/test/ERC20.t.sol index d2b7535..4c70a22 100644 --- a/contracts/test/ERC20.t.sol +++ b/contracts/test/ERC20.t.sol @@ -1,17 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.7; -import { DSTest } from "../../modules/ds-test/src/test.sol"; +import { InvariantTest, TestUtils } from "../../modules/contract-test-utils/contracts/test.sol"; -import { ERC20User } from "./accounts/ERC20User.sol"; -import { MockERC20 } from "./mocks/MockERC20.sol"; +import { ERC20User } from "./accounts/ERC20User.sol"; +import { MockERC20 } from "./mocks/MockERC20.sol"; -import { InvariantTest } from "./utils/InvariantTest.sol"; -import { Vm } from "./utils/Vm.sol"; - -contract ERC20Test is DSTest { - - Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); +contract ERC20Test is TestUtils { bytes constant ARITHMETIC_ERROR = abi.encodeWithSignature("Panic(uint256)", 0x11); @@ -60,6 +55,32 @@ contract ERC20Test is DSTest { assertEq(token.allowance(self, account), amount); } + function test_increaseAllowance(address account, uint256 initialAmount, uint256 addedAmount) public { + initialAmount = constrictToRange(initialAmount, 0, type(uint256).max / 2); + addedAmount = constrictToRange(addedAmount, 0, type(uint256).max / 2); + + token.approve(account, initialAmount); + + assertEq(token.allowance(self, account), initialAmount); + + assertTrue(token.increaseAllowance(account, addedAmount)); + + assertEq(token.allowance(self, account), initialAmount + addedAmount); + } + + function test_decreaseAllowance(address account, uint256 initialAmount, uint256 subtractedAmount) public { + initialAmount = constrictToRange(initialAmount, 0, type(uint256).max); + subtractedAmount = constrictToRange(subtractedAmount, 0, initialAmount); + + token.approve(account, initialAmount); + + assertEq(token.allowance(self, account), initialAmount); + + assertTrue(token.decreaseAllowance(account, subtractedAmount)); + + assertEq(token.allowance(self, account), initialAmount - subtractedAmount); + } + function test_transfer(address account, uint256 amount) public { token.mint(self, amount); @@ -152,7 +173,7 @@ contract ERC20Test is DSTest { } -contract ERC20Invariants is DSTest, InvariantTest { +contract ERC20Invariants is TestUtils, InvariantTest { BalanceSum balanceSum; diff --git a/contracts/test/ERC20Permit.t.sol b/contracts/test/ERC20Permit.t.sol index 6c638bb..5651df4 100644 --- a/contracts/test/ERC20Permit.t.sol +++ b/contracts/test/ERC20Permit.t.sol @@ -1,17 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.7; -import { DSTest } from "../../modules/ds-test/src/test.sol"; +import { InvariantTest, TestUtils } from "../../modules/contract-test-utils/contracts/test.sol"; import { ERC20Permit } from "../ERC20Permit.sol"; -import { ERC20PermitUser } from "./accounts/ERC20User.sol"; - -import { MockERC20Permit } from "./mocks/MockERC20.sol"; - -import { Vm } from "./utils/Vm.sol"; -import { InvariantTest } from "./utils/InvariantTest.sol"; - +import { ERC20PermitUser } from "./accounts/ERC20User.sol"; +import { MockERC20Permit } from "./mocks/MockERC20.sol"; import { ERC20Test, MockERC20 } from "./ERC20.t.sol"; contract ERC20PermitBaseTest is ERC20Test { @@ -22,9 +17,7 @@ contract ERC20PermitBaseTest is ERC20Test { } -contract ERC20PermitTest is DSTest { - - Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); +contract ERC20PermitTest is TestUtils { bytes constant ARITHMETIC_ERROR = abi.encodeWithSignature("Panic(uint256)", 0x11); @@ -155,7 +148,7 @@ contract ERC20PermitTest is DSTest { } -contract ERC20Invariants is DSTest, InvariantTest { +contract ERC20Invariants is TestUtils, InvariantTest { BalanceSum balanceSum; diff --git a/modules/contract-test-utils b/modules/contract-test-utils new file mode 160000 index 0000000..3bda523 --- /dev/null +++ b/modules/contract-test-utils @@ -0,0 +1 @@ +Subproject commit 3bda52378740c3d756ed0a90373f306a20291941 diff --git a/modules/ds-test b/modules/ds-test deleted file mode 160000 index 0a5da56..0000000 --- a/modules/ds-test +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0a5da56b0d65960e6a994d2ec8245e6edd38c248