Skip to content

Commit

Permalink
Merge branch 'master' into simplify-721-reasons
Browse files Browse the repository at this point in the history
  • Loading branch information
frangio committed Jun 1, 2022
2 parents ca7d42f + 4fc9fd3 commit c2475b4
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 36 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -11,8 +11,10 @@
* `EnumerableMap`: add new `Bytes32ToUintMap` map type. ([#3416](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3416))
* `SafeCast`: add support for many more types, using procedural code generation. ([#3245](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3245))
* `MerkleProof`: add `multiProofVerify` to prove multiple values are part of a Merkle tree. ([#3276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3276))
* `MerkleProof`: add calldata versions of the functions to avoid copying input arrays to memory and save gas. ([#3200](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3200))
* `ERC721`, `ERC1155`: simplified revert reasons. ([#3254](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3254), ([#3438](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3438)))
* `ERC721`: removed redundant require statement. ([#3434](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3434))
* `PaymentSplitter`: add `releasable` getters. ([#3350](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3350))

## 4.6.0 (2022-04-26)

Expand Down
4 changes: 2 additions & 2 deletions contracts/crosschain/README.adoc
Expand Up @@ -9,7 +9,7 @@ This directory provides building blocks to improve cross-chain awareness of smar
== CrossChainEnabled specializations

The following specializations of {CrossChainEnabled} provide implementations of the {CrossChainEnabled} abstraction for specific bridges. This can be used to complexe cross-chain aware components such as {AccessControlCrossChain}.
The following specializations of {CrossChainEnabled} provide implementations of the {CrossChainEnabled} abstraction for specific bridges. This can be used to complex cross-chain aware components such as {AccessControlCrossChain}.

{{CrossChainEnabledAMB}}

Expand All @@ -23,7 +23,7 @@ The following specializations of {CrossChainEnabled} provide implementations of

== Libraries for cross-chain

In addition to the {CrossChainEnable} abstraction, cross-chain awareness is also available through libraries. These libraries can be used to build complex designs such as contracts with the ability to interact with multiple bridges.
In addition to the {CrossChainEnabled} abstraction, cross-chain awareness is also available through libraries. These libraries can be used to build complex designs such as contracts with the ability to interact with multiple bridges.

{{LibAMB}}

Expand Down
23 changes: 19 additions & 4 deletions contracts/finance/PaymentSplitter.sol
Expand Up @@ -120,15 +120,31 @@ contract PaymentSplitter is Context {
return _payees[index];
}

/**
* @dev Getter for the amount of payee's releasable Ether.
*/
function releasable(address account) public view returns (uint256) {
uint256 totalReceived = address(this).balance + totalReleased();
return _pendingPayment(account, totalReceived, released(account));
}

/**
* @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an
* IERC20 contract.
*/
function releasable(IERC20 token, address account) public view returns (uint256) {
uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);
return _pendingPayment(account, totalReceived, released(token, account));
}

/**
* @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the
* total shares and their previous withdrawals.
*/
function release(address payable account) public virtual {
require(_shares[account] > 0, "PaymentSplitter: account has no shares");

uint256 totalReceived = address(this).balance + totalReleased();
uint256 payment = _pendingPayment(account, totalReceived, released(account));
uint256 payment = releasable(account);

require(payment != 0, "PaymentSplitter: account is not due payment");

Expand All @@ -147,8 +163,7 @@ contract PaymentSplitter is Context {
function release(IERC20 token, address account) public virtual {
require(_shares[account] > 0, "PaymentSplitter: account has no shares");

uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token);
uint256 payment = _pendingPayment(account, totalReceived, released(token, account));
uint256 payment = releasable(token, account);

require(payment != 0, "PaymentSplitter: account is not due payment");

Expand Down
24 changes: 18 additions & 6 deletions contracts/mocks/MerkleProofWrapper.sol
Expand Up @@ -13,23 +13,35 @@ contract MerkleProofWrapper {
return MerkleProof.verify(proof, root, leaf);
}

function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) public pure returns (bool) {
return MerkleProof.verifyCalldata(proof, root, leaf);
}

function processProof(bytes32[] memory proof, bytes32 leaf) public pure returns (bytes32) {
return MerkleProof.processProof(proof, leaf);
}

function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) public pure returns (bytes32) {
return MerkleProof.processProofCalldata(proof, leaf);
}

function multiProofVerify(
bytes32 root,
bytes32[] memory leafs,
bytes32[] memory proofs,
bool[] memory proofFlag
bytes32[] calldata leafs,
bytes32[] calldata proofs,
bool[] calldata proofFlag
) public pure returns (bool) {
return MerkleProof.multiProofVerify(root, leafs, proofs, proofFlag);
}

function processMultiProof(
bytes32[] memory leafs,
bytes32[] memory proofs,
bool[] memory proofFlag
bytes32[] calldata leafs,
bytes32[] calldata proofs,
bool[] calldata proofFlag
) public pure returns (bytes32) {
return MerkleProof.processMultiProof(leafs, proofs, proofFlag);
}
Expand Down
59 changes: 45 additions & 14 deletions contracts/utils/cryptography/MerkleProof.sol
Expand Up @@ -32,6 +32,19 @@ library MerkleProof {
return processProof(proof, leaf) == root;
}

/**
* @dev Calldata version of {verify}
*
* _Available since v4.7._
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}

/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
Expand All @@ -48,6 +61,19 @@ library MerkleProof {
return computedHash;
}

/**
* @dev Calldata version of {processProof}
*
* _Available since v4.7._
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}

/**
* @dev Returns true if a `leafs` can be proved to be a part of a Merkle tree
* defined by `root`. For this, `proofs` for each leaf must be provided, containing
Expand All @@ -58,11 +84,11 @@ library MerkleProof {
*/
function multiProofVerify(
bytes32 root,
bytes32[] memory leafs,
bytes32[] memory proofs,
bool[] memory proofFlag
bytes32[] calldata leaves,
bytes32[] calldata proofs,
bool[] calldata proofFlag
) internal pure returns (bool) {
return processMultiProof(leafs, proofs, proofFlag) == root;
return processMultiProof(leaves, proofs, proofFlag) == root;
}

/**
Expand All @@ -73,20 +99,19 @@ library MerkleProof {
* _Available since v4.7._
*/
function processMultiProof(
bytes32[] memory leafs,
bytes32[] memory proofs,
bool[] memory proofFlag
bytes32[] calldata leaves,
bytes32[] calldata proofs,
bool[] calldata proofFlag
) internal pure returns (bytes32 merkleRoot) {
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leafs` array, then goes onto the
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leafsLen = leafs.length;
uint256 proofsLen = proofs.length;
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlag.length;

// Check proof validity.
require(leafsLen + proofsLen - 1 == totalHashes, "MerkleProof: invalid multiproof");
require(leavesLen + proofs.length - 1 == totalHashes, "MerkleProof: invalid multiproof");

// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
Expand All @@ -100,12 +125,18 @@ library MerkleProof {
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
// `proofs` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leafsLen ? leafs[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlag[i] ? leafPos < leafsLen ? leafs[leafPos++] : hashes[hashPos++] : proofs[proofPos++];
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlag[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proofs[proofPos++];
hashes[i] = _hashPair(a, b);
}

return hashes[totalHashes - 1];
if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proofs[0];
}
}

function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
Expand Down
33 changes: 27 additions & 6 deletions test/finance/PaymentSplitter.test.js
Expand Up @@ -62,10 +62,11 @@ contract('PaymentSplitter', function (accounts) {
await Promise.all(this.payees.map(async (payee, index) => {
expect(await this.contract.payee(index)).to.equal(payee);
expect(await this.contract.released(payee)).to.be.bignumber.equal('0');
expect(await this.contract.releasable(payee)).to.be.bignumber.equal('0');
}));
});

describe('accepts payments', async function () {
describe('accepts payments', function () {
it('Ether', async function () {
await send.ether(owner, this.contract.address, amount);

Expand All @@ -79,7 +80,7 @@ contract('PaymentSplitter', function (accounts) {
});
});

describe('shares', async function () {
describe('shares', function () {
it('stores shares if address is payee', async function () {
expect(await this.contract.shares(payee1)).to.be.bignumber.not.equal('0');
});
Expand All @@ -89,8 +90,8 @@ contract('PaymentSplitter', function (accounts) {
});
});

describe('release', async function () {
describe('Ether', async function () {
describe('release', function () {
describe('Ether', function () {
it('reverts if no funds to claim', async function () {
await expectRevert(this.contract.release(payee1),
'PaymentSplitter: account is not due payment',
Expand All @@ -104,7 +105,7 @@ contract('PaymentSplitter', function (accounts) {
});
});

describe('Token', async function () {
describe('Token', function () {
it('reverts if no funds to claim', async function () {
await expectRevert(this.contract.release(this.token.address, payee1),
'PaymentSplitter: account is not due payment',
Expand All @@ -119,7 +120,27 @@ contract('PaymentSplitter', function (accounts) {
});
});

describe('distributes funds to payees', async function () {
describe('tracks releasable and released', function () {
it('Ether', async function () {
await send.ether(payer1, this.contract.address, amount);
const payment = amount.divn(10);
expect(await this.contract.releasable(payee2)).to.be.bignumber.equal(payment);
await this.contract.release(payee2);
expect(await this.contract.releasable(payee2)).to.be.bignumber.equal('0');
expect(await this.contract.released(payee2)).to.be.bignumber.equal(payment);
});

it('Token', async function () {
await this.token.transfer(this.contract.address, amount, { from: owner });
const payment = amount.divn(10);
expect(await this.contract.releasable(this.token.address, payee2, {})).to.be.bignumber.equal(payment);
await this.contract.release(this.token.address, payee2);
expect(await this.contract.releasable(this.token.address, payee2, {})).to.be.bignumber.equal('0');
expect(await this.contract.released(this.token.address, payee2)).to.be.bignumber.equal(payment);
});
});

describe('distributes funds to payees', function () {
it('Ether', async function () {
await send.ether(payer1, this.contract.address, amount);

Expand Down
32 changes: 28 additions & 4 deletions test/utils/cryptography/MerkleProof.test.js
Expand Up @@ -25,12 +25,14 @@ contract('MerkleProof', function (accounts) {
const proof = merkleTree.getHexProof(leaf);

expect(await this.merkleProof.verify(proof, root, leaf)).to.equal(true);
expect(await this.merkleProof.verifyCalldata(proof, root, leaf)).to.equal(true);

// For demonstration, it is also possible to create valid proofs for certain 64-byte values *not* in elements:
const noSuchLeaf = keccak256(
Buffer.concat([keccak256(elements[0]), keccak256(elements[1])].sort(Buffer.compare)),
);
expect(await this.merkleProof.verify(proof.slice(1), root, noSuchLeaf)).to.equal(true);
expect(await this.merkleProof.verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.equal(true);
});

it('returns false for an invalid Merkle proof', async function () {
Expand All @@ -47,6 +49,7 @@ contract('MerkleProof', function (accounts) {
const badProof = badMerkleTree.getHexProof(badElements[0]);

expect(await this.merkleProof.verify(badProof, correctRoot, correctLeaf)).to.equal(false);
expect(await this.merkleProof.verifyCalldata(badProof, correctRoot, correctLeaf)).to.equal(false);
});

it('returns false for a Merkle proof of invalid length', async function () {
Expand All @@ -61,6 +64,7 @@ contract('MerkleProof', function (accounts) {
const badProof = proof.slice(0, proof.length - 5);

expect(await this.merkleProof.verify(badProof, root, leaf)).to.equal(false);
expect(await this.merkleProof.verifyCalldata(badProof, root, leaf)).to.equal(false);
});
});

Expand Down Expand Up @@ -93,15 +97,15 @@ contract('MerkleProof', function (accounts) {
it('revert with invalid multi proof #1', async function () {
const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch
const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
const badLeave = keccak256('e');
const badLeaf = keccak256('e');
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });

const root = merkleTree.getRoot();

await expectRevert(
this.merkleProof.multiProofVerify(
root,
[ leaves[0], badLeave ], // A, E
[ leaves[0], badLeaf ], // A, E
[ leaves[1], fill, merkleTree.layers[1][1] ],
[ false, false, false ],
),
Expand All @@ -112,20 +116,40 @@ contract('MerkleProof', function (accounts) {
it('revert with invalid multi proof #2', async function () {
const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch
const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
const badLeave = keccak256('e');
const badLeaf = keccak256('e');
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });

const root = merkleTree.getRoot();

await expectRevert(
this.merkleProof.multiProofVerify(
root,
[ badLeave, leaves[0] ], // A, E
[ badLeaf, leaves[0] ], // A, E
[ leaves[1], fill, merkleTree.layers[1][1] ],
[ false, false, false, false ],
),
'reverted with panic code 0x32',
);
});

it('limit case: works for tree containing a single leaf', async function () {
const leaves = ['a'].map(keccak256).sort(Buffer.compare);
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });

const root = merkleTree.getRoot();
const proofLeaves = ['a'].map(keccak256).sort(Buffer.compare);
const proof = merkleTree.getMultiProof(proofLeaves);
const proofFlags = merkleTree.getProofFlags(proofLeaves, proof);

expect(await this.merkleProof.multiProofVerify(root, proofLeaves, proof, proofFlags)).to.equal(true);
});

it('limit case: can prove empty leaves', async function () {
const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare);
const merkleTree = new MerkleTree(leaves, keccak256, { sort: true });

const root = merkleTree.getRoot();
expect(await this.merkleProof.multiProofVerify(root, [], [ root ], [])).to.equal(true);
});
});
});

0 comments on commit c2475b4

Please sign in to comment.