Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

💥 Implement Additional Math Functions #86

Merged
merged 10 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 38 additions & 23 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ BatchDistributorTest:testDistributeTokenMultipleAddressesSuccess() (gas: 764652)
BatchDistributorTest:testDistributeTokenOneAddressSuccess() (gas: 727265)
BatchDistributorTest:testDistributeTokenRevertWithInsufficientAllowance() (gas: 721109)
BatchDistributorTest:testDistributeTokenRevertWithInsufficientBalance() (gas: 723840)
BatchDistributorTest:testFuzzDistributeEtherMultipleAddressesSuccess(((address,uint256)[]),uint256) (runs: 256, μ: 988402, ~: 925503)
BatchDistributorTest:testFuzzDistributeEtherMultipleAddressesSuccess(((address,uint256)[]),uint256) (runs: 256, μ: 988506, ~: 925621)
BatchDistributorTest:testFuzzDistributeTokenMultipleAddressesSuccess(((address,uint256)[]),address,uint256) (runs: 256, μ: 1465572, ~: 1430877)
Create2AddressTest:testComputeAddress() (gas: 702836)
Create2AddressTest:testComputeAddressSelf() (gas: 711578)
Expand All @@ -86,32 +86,32 @@ CreateAddressTest:testComputeAddressSelfNonceUint56() (gas: 688873)
CreateAddressTest:testComputeAddressSelfNonceUint64() (gas: 688969)
CreateAddressTest:testComputeAddressSelfNonceUint8() (gas: 688618)
CreateAddressTest:testComputeAddressSelfRevertTooHighNonce() (gas: 9122)
CreateAddressTest:testFuzzComputeAddressNonce0x7f(uint64,address) (runs: 256, μ: 687669, ~: 687831)
CreateAddressTest:testFuzzComputeAddressNonce0x7f(uint64,address) (runs: 256, μ: 687670, ~: 687831)
CreateAddressTest:testFuzzComputeAddressNonceUint16(uint64,address) (runs: 256, μ: 687167, ~: 687294)
CreateAddressTest:testFuzzComputeAddressNonceUint24(uint64,address) (runs: 256, μ: 687297, ~: 687398)
CreateAddressTest:testFuzzComputeAddressNonceUint24(uint64,address) (runs: 256, μ: 687295, ~: 687398)
CreateAddressTest:testFuzzComputeAddressNonceUint32(uint64,address) (runs: 256, μ: 687293, ~: 687427)
CreateAddressTest:testFuzzComputeAddressNonceUint40(uint64,address) (runs: 256, μ: 687399, ~: 687497)
CreateAddressTest:testFuzzComputeAddressNonceUint48(uint64,address) (runs: 256, μ: 687399, ~: 687503)
CreateAddressTest:testFuzzComputeAddressNonceUint48(uint64,address) (runs: 256, μ: 687398, ~: 687503)
CreateAddressTest:testFuzzComputeAddressNonceUint56(uint64,address) (runs: 256, μ: 687403, ~: 687510)
CreateAddressTest:testFuzzComputeAddressNonceUint64(uint64,address) (runs: 256, μ: 687431, ~: 687614)
CreateAddressTest:testFuzzComputeAddressNonceUint8(uint64,address) (runs: 256, μ: 687169, ~: 687272)
CreateAddressTest:testFuzzComputeAddressRevertTooHighNonce(uint256,address) (runs: 256, μ: 13321, ~: 13275)
CreateAddressTest:testFuzzComputeAddressSelfNonce0x7f(uint64) (runs: 256, μ: 693917, ~: 694033)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint16(uint64) (runs: 256, μ: 693196, ~: 693106)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint24(uint64) (runs: 256, μ: 693545, ~: 693631)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint16(uint64) (runs: 256, μ: 693198, ~: 693106)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint24(uint64) (runs: 256, μ: 693546, ~: 693631)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint32(uint64) (runs: 256, μ: 693545, ~: 693639)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint40(uint64) (runs: 256, μ: 693627, ~: 693710)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint48(uint64) (runs: 256, μ: 693587, ~: 693697)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint56(uint64) (runs: 256, μ: 693679, ~: 693767)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint56(uint64) (runs: 256, μ: 693678, ~: 693767)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint64(uint64) (runs: 256, μ: 693746, ~: 693897)
CreateAddressTest:testFuzzComputeAddressSelfNonceUint8(uint64) (runs: 256, μ: 693258, ~: 693334)
CreateAddressTest:testFuzzComputeAddressSelfRevertTooHighNonce(uint256) (runs: 256, μ: 13522, ~: 13480)
ECDSATest:testEthSignedMessageHash() (gas: 5859)
ECDSATest:testFuzzEthSignedMessageHash(string) (runs: 256, μ: 6452, ~: 6455)
ECDSATest:testFuzzRecoverWithInvalidSignature(bytes,string) (runs: 256, μ: 15181, ~: 15182)
ECDSATest:testFuzzRecoverWithTooLongSignature(bytes,string) (runs: 256, μ: 13144, ~: 13142)
ECDSATest:testFuzzRecoverWithValidSignature(string,string) (runs: 256, μ: 21787, ~: 21796)
ECDSATest:testFuzzRecoverWithWrongMessage(string,string,bytes32) (runs: 256, μ: 21775, ~: 21789)
ECDSATest:testFuzzRecoverWithValidSignature(string,string) (runs: 256, μ: 21789, ~: 21796)
ECDSATest:testFuzzRecoverWithWrongMessage(string,string,bytes32) (runs: 256, μ: 21773, ~: 21789)
ECDSATest:testFuzzToDataWithIntendedValidatorHash(address,bytes) (runs: 256, μ: 7069, ~: 7057)
ECDSATest:testFuzzToDataWithIntendedValidatorHashSelf(bytes) (runs: 256, μ: 10062, ~: 10045)
ECDSATest:testFuzzToTypedDataHash(string,string) (runs: 256, μ: 7105, ~: 7108)
Expand Down Expand Up @@ -262,8 +262,8 @@ ERC1155Test:testUriBaseAndTokenUriNotSet() (gas: 2993638)
ERC1155Test:testUriBaseAndTokenUriSet() (gas: 66159)
ERC1155Test:testUriNoBaseURI() (gas: 3044520)
ERC1155Test:testUriNoTokenUri() (gas: 16929)
ERC20Invariants:invariantOwner() (runs: 256, calls: 3840, reverts: 3135)
ERC20Invariants:invariantTotalSupply() (runs: 256, calls: 3840, reverts: 3135)
ERC20Invariants:invariantOwner() (runs: 256, calls: 3840, reverts: 3132)
ERC20Invariants:invariantTotalSupply() (runs: 256, calls: 3840, reverts: 3132)
ERC20Test:testApproveExceedingBalanceCase1() (gas: 40595)
ERC20Test:testApproveExceedingBalanceCase2() (gas: 47293)
ERC20Test:testApproveFromZeroAddress() (gas: 13278)
Expand Down Expand Up @@ -355,8 +355,8 @@ ERC20Test:testTransferOwnershipToZeroAddress() (gas: 15837)
ERC20Test:testTransferSuccess() (gas: 43220)
ERC20Test:testTransferToZeroAddress() (gas: 16812)
ERC20Test:testTransferZeroTokens() (gas: 25327)
ERC4626VaultInvariants:invariantTotalAssets() (runs: 256, calls: 3840, reverts: 2978)
ERC4626VaultInvariants:invariantTotalSupply() (runs: 256, calls: 3840, reverts: 2978)
ERC4626VaultInvariants:invariantTotalAssets() (runs: 256, calls: 3840, reverts: 2977)
ERC4626VaultInvariants:invariantTotalSupply() (runs: 256, calls: 3840, reverts: 2977)
ERC4626VaultTest:testCachedDomainSeparator() (gas: 7836)
ERC4626VaultTest:testDepositInsufficientAllowance() (gas: 83276)
ERC4626VaultTest:testDepositWithNoApproval() (gas: 23849)
Expand Down Expand Up @@ -515,16 +515,31 @@ ERC721Test:testTransferFrom() (gas: 582560)
ERC721Test:testTransferOwnershipNonOwner() (gas: 13015)
ERC721Test:testTransferOwnershipSuccess() (gas: 55029)
ERC721Test:testTransferOwnershipToZeroAddress() (gas: 16064)
MathTest:testFuzzMulDiv(uint256,uint256,uint256) (runs: 256, μ: 12841, ~: 12595)
MathTest:testFuzzMulDivDomain(uint256,uint256,uint256) (runs: 256, μ: 10347, ~: 10430)
MathTest:testMulDivDivisionByZero() (gas: 10214)
MathTest:testMulDivOverflow() (gas: 10658)
MathTest:testMulDivRoundDownLargeValues() (gas: 12568)
MathTest:testMulDivRoundDownSmallValues() (gas: 7571)
MathTest:testMulDivRoundUpLargeValues() (gas: 12719)
MathTest:testMulDivRoundUpSmallValues() (gas: 7758)
MerkleProofVerificationTest:testFuzzMultiProofVerifySingleLeaf(bytes32[],uint256) (runs: 256, μ: 1649977824, ~: 1649974667)
MerkleProofVerificationTest:testFuzzVerify(bytes32[],uint256) (runs: 256, μ: 135976479, ~: 135973293)
MathTest:testCeilDiv() (gas: 12818)
MathTest:testFuzzCeilDiv(uint256,uint256) (runs: 256, μ: 9048, ~: 9065)
MathTest:testFuzzInt256Average(int256,int256) (runs: 256, μ: 5948, ~: 5948)
MathTest:testFuzzLog10(uint256,bool) (runs: 256, μ: 7135, ~: 7362)
MathTest:testFuzzLog2(uint256,bool) (runs: 256, μ: 7020, ~: 7138)
MathTest:testFuzzLog256(uint256,bool) (runs: 256, μ: 7141, ~: 7366)
MathTest:testFuzzMulDiv(uint256,uint256,uint256) (runs: 256, μ: 12873, ~: 12618)
MathTest:testFuzzMulDivDomain(uint256,uint256,uint256) (runs: 256, μ: 10358, ~: 10433)
MathTest:testFuzzUint256Average(uint256,uint256) (runs: 256, μ: 5756, ~: 5756)
MathTest:testInt256Average() (gas: 12392)
MathTest:testLog10RoundDown() (gas: 19030)
MathTest:testLog10RoundUp() (gas: 20130)
MathTest:testLog256RoundDown() (gas: 15896)
MathTest:testLog256RoundUp() (gas: 16815)
MathTest:testLog2RoundDown() (gas: 17978)
MathTest:testLog2RoundUp() (gas: 18835)
MathTest:testMulDivDivisionByZero() (gas: 10347)
MathTest:testMulDivOverflow() (gas: 10681)
MathTest:testMulDivRoundDownLargeValues() (gas: 12524)
MathTest:testMulDivRoundDownSmallValues() (gas: 7617)
MathTest:testMulDivRoundUpLargeValues() (gas: 12742)
MathTest:testMulDivRoundUpSmallValues() (gas: 7783)
MathTest:testUint256Average() (gas: 8381)
MerkleProofVerificationTest:testFuzzMultiProofVerifySingleLeaf(bytes32[],uint256) (runs: 256, μ: 1649977823, ~: 1649974519)
MerkleProofVerificationTest:testFuzzVerify(bytes32[],uint256) (runs: 256, μ: 135976478, ~: 135973145)
MerkleProofVerificationTest:testFuzzVerifyMultiProofMultipleLeaves(bool,bool,bool) (runs: 256, μ: 412472500, ~: 412472432)
MerkleProofVerificationTest:testInvalidMerkleMultiProof() (gas: 412478563)
MerkleProofVerificationTest:testInvalidMerkleProof() (gas: 33970453)
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
- `ECDSA`: Elliptic curve digital signature algorithm (ECDSA) functions.
- `SignatureChecker`: ECDSA and EIP-1271 signature verification functions.
- `EIP712DomainSeparator`: EIP-712 domain separator.
- `Math`: Standard mathematical utility functions. ([#74](https://github.com/pcaversaccio/snekmate/pull/74))
- `Math`: Standard mathematical utility functions. ([#74](https://github.com/pcaversaccio/snekmate/pull/74), [#86](https://github.com/pcaversaccio/snekmate/pull/86))
- `MerkleProofVerification`: Merkle tree proof verification functions. ([#30](https://github.com/pcaversaccio/snekmate/pull/30))
- `Multicall`: Multicall functions.
2 changes: 1 addition & 1 deletion lib/create-util
2 changes: 1 addition & 1 deletion lib/openzeppelin-contracts
2 changes: 1 addition & 1 deletion lib/prb-test
Submodule prb-test updated 5 files
+14 −0 CHANGELOG.md
+3 −1 README.md
+1 −1 package.json
+54 −54 src/PRBTest.sol
+6 −0 src/Vm.sol
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@
"keccak256": "^1.0.6",
"merkletreejs": "^0.3.9",
"prettier": "^2.8.4",
"prettier-plugin-solidity": "^1.1.2"
"prettier-plugin-solidity": "^1.1.3"
}
}
211 changes: 208 additions & 3 deletions src/utils/Math.vy
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ def mul_div(x: uint256, y: uint256, denominator: uint256, roundup: bool) -> uint
following the selected rounding direction.
@notice The implementation is inspired by Remco Bloemen's
implementation under the MIT license here:
https://xn--2-umb.com/21/muldiv. Furthermore,
the rounding direction design pattern is inspired
by OpenZeppelin's implementation here:
https://xn--2-umb.com/21/muldiv.
Furthermore, the rounding direction design pattern is
inspired by OpenZeppelin's implementation here:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/Math.sol.
@param x The 32-byte multiplicand.
@param y The 32-byte multiplier.
Expand Down Expand Up @@ -130,3 +130,208 @@ def mul_div(x: uint256, y: uint256, denominator: uint256, roundup: bool) -> uint
result += 1

return result


@external
@pure
def uint256_average(x: uint256, y: uint256) -> uint256:
"""
@dev Returns the average of two 32-byte unsigned integers.
@notice Note that the result is rounded towards zero. For
more details on finding the average of two unsigned
integers without an overflow, please refer to:
https://devblogs.microsoft.com/oldnewthing/20220207-00/?p=106223.
@param x The first 32-byte unsigned integer of the data set.
@param y The second 32-byte unsigned integer of the data set.
@return uint256 The 32-byte average (rounded towards zero) of
`x` and `y`.
"""
return unsafe_add(x & y, shift(x ^ y, -1))


@external
@pure
def int256_average(x: int256, y: int256) -> int256:
"""
@dev Returns the average of two 32-byte signed integers.
@notice Note that the result is rounded towards infinity.
For more details on finding the average of two signed
integers without an overflow, please refer to:
https://patents.google.com/patent/US6007232A/en.
@param x The first 32-byte signed integer of the data set.
@param y The second 32-byte signed integer of the data set.
@return uint256 The 32-byte average (rounded towards infinity)
of `x` and `y`.
"""
return unsafe_add(unsafe_add(shift(x, -1), shift(y, -1)), x & y & 1)


@external
@pure
def ceil_div(x: uint256, y: uint256) -> uint256:
"""
@dev Calculates "ceil(x / y)" for any strictly positive `y`.
@notice The implementation is inspired by OpenZeppelin's
implementation here:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/Math.sol.
@param x The 32-byte numerator.
@param y The 32-byte denominator.
@return uint256 The 32-byte rounded up result of "x/y".
"""
assert y != empty(uint256), "Math: ceil_div division by zero"
if (x == empty(uint256)):
return empty(uint256)
else:
return unsafe_add(unsafe_div(x - 1, y), 1)


@external
@pure
def log_2(x: uint256, roundup: bool) -> uint256:
"""
@dev Returns the log in base 2 of `x`, following the selected
rounding direction.
@notice Note that it returns 0 if given 0. The implementation is
inspired by OpenZeppelin's implementation here:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/Math.sol.
@param x The 32-byte variable.
@param roundup The Boolean variable that specifies whether
to round up or not. The default `False` is round down.
@return uint256 The 32-byte calculation result.
"""
value: uint256 = x
result: uint256 = empty(uint256)

if(x == empty(uint256)):
# For the special case `x == 0` we already return 0 here in order
# not to iterate through the remaining code.
return empty(uint256)

# The following lines cannot overflow because we have the well-known
# decay behaviour of `log_2(max_value(uint256)) < max_value(uint256)`.
if (shift(x, -128) != empty(uint256)):
value = shift(x, -128)
result = 128
if (shift(value, -64) != empty(uint256)):
value = shift(value, -64)
result = unsafe_add(result, 64)
if (shift(value, -32) != empty(uint256)):
value = shift(value, -32)
result = unsafe_add(result, 32)
if (shift(value, -16) != empty(uint256)):
value = shift(value, -16)
result = unsafe_add(result, 16)
if (shift(value, -8) != empty(uint256)):
value = shift(value, -8)
result = unsafe_add(result, 8)
if (shift(value, -4) != empty(uint256)):
value = shift(value, -4)
result = unsafe_add(result, 4)
if (shift(value, -2) != empty(uint256)):
value = shift(value, -2)
result = unsafe_add(result, 2)
if (shift(value, -1) != empty(uint256)):
result = unsafe_add(result, 1)

if (roundup and (shift(1, convert(result, int256)) < x)):
result = unsafe_add(result, 1)

return result


@external
@pure
def log_10(x: uint256, roundup: bool) -> uint256:
"""
@dev Returns the log in base 10 of `x`, following the selected
rounding direction.
@notice Note that it returns 0 if given 0. The implementation is
inspired by OpenZeppelin's implementation here:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/Math.sol.
@param x The 32-byte variable.
@param roundup The Boolean variable that specifies whether
to round up or not. The default `False` is round down.
@return uint256 The 32-byte calculation result.
"""
value: uint256 = x
result: uint256 = empty(uint256)

if(x == empty(uint256)):
# For the special case `x == 0` we already return 0 here in order
# not to iterate through the remaining code.
return empty(uint256)

# The following lines cannot overflow because we have the well-known
# decay behaviour of `log_10(max_value(uint256)) < max_value(uint256)`.
if (x >= 10 ** 64):
value = unsafe_div(x, 10 ** 64)
result = 64
if (value >= 10 ** 32):
value = unsafe_div(value, 10 ** 32)
result = unsafe_add(result, 32)
if (value >= 10 ** 16):
value = unsafe_div(value, 10 ** 16)
result = unsafe_add(result, 16)
if (value >= 10 ** 8):
value = unsafe_div(value, 10 ** 8)
result = unsafe_add(result, 8)
if (value >= 10 ** 4):
value = unsafe_div(value, 10 ** 4)
result = unsafe_add(result, 4)
if (value >= 10 ** 2):
value = unsafe_div(value, 10 ** 2)
result = unsafe_add(result, 2)
if (value >= 10):
result = unsafe_add(result, 1)

if (roundup and (10 ** result < x)):
result = unsafe_add(result, 1)

return result


@external
@pure
def log_256(x: uint256, roundup: bool) -> uint256:
"""
@dev Returns the log in base 256 of `x`, following the selected
rounding direction.
@notice Note that it returns 0 if given 0. Also, adding one to the
rounded down result gives the number of pairs of hex symbols
needed to represent `x` as a hex string. The implementation is
inspired by OpenZeppelin's implementation here:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/Math.sol.
@param x The 32-byte variable.
@param roundup The Boolean variable that specifies whether
to round up or not. The default `False` is round down.
@return uint256 The 32-byte calculation result.
"""
value: uint256 = x
result: uint256 = empty(uint256)

if(x == empty(uint256)):
# For the special case `x == 0` we already return 0 here in order
# not to iterate through the remaining code.
return empty(uint256)

# The following lines cannot overflow because we have the well-known
# decay behaviour of `log_256(max_value(uint256)) < max_value(uint256)`.
if (shift(x, -128) != empty(uint256)):
value = shift(x, -128)
result = 16
if (shift(value, -64) != empty(uint256)):
value = shift(value, -64)
result = unsafe_add(result, 8)
if (shift(value, -32) != empty(uint256)):
value = shift(value, -32)
result = unsafe_add(result, 4)
if (shift(value, -16) != empty(uint256)):
value = shift(value, -16)
result = unsafe_add(result, 2)
if (shift(value, -8) != empty(uint256)):
result = unsafe_add(result, 1)

if (roundup and (shift(1, convert(shift(result, 3), int256)) < x)):
result = unsafe_add(result, 1)

return result
Loading