diff --git a/contracts/oracle/price/TwoPriceOracleFactory.sol b/contracts/oracle/price/TwoPriceOracleFactory.sol index 99517deb..e7b6381d 100644 --- a/contracts/oracle/price/TwoPriceOracleFactory.sol +++ b/contracts/oracle/price/TwoPriceOracleFactory.sol @@ -25,7 +25,7 @@ contract TwoPriceOracleFactory is Factory { /// parameters are already encoded. /// /// @param config_ Config for the oracle. - /// @return New `ChainlinkFeedPriceOracle` child contract address. + /// @return New `TwoPriceOracle` child contract address. function createChildTyped( TwoPriceOracleConfig memory config_ ) external returns (TwoPriceOracle) { diff --git a/contracts/test/TestReceiptOwner.sol b/contracts/test/TestReceiptOwner.sol index a1040682..223ad0fb 100644 --- a/contracts/test/TestReceiptOwner.sol +++ b/contracts/test/TestReceiptOwner.sol @@ -58,7 +58,7 @@ contract TestReceiptOwner is IReceiptOwnerV1 { uint256 amount_, bytes memory data_ ) external { - receipt_.ownerMint(account_, id_, amount_, data_); + receipt_.ownerMint(msg.sender, account_, id_, amount_, data_); } /// Exposes `IReceiptV1.ownerBurn` to anon. @@ -74,7 +74,13 @@ contract TestReceiptOwner is IReceiptOwnerV1 { uint256 amount_, bytes memory receiptInformation_ ) external { - receipt_.ownerBurn(account_, id_, amount_, receiptInformation_); + receipt_.ownerBurn( + msg.sender, + account_, + id_, + amount_, + receiptInformation_ + ); } /// Exposes `IReceiptV1.ownerTransferFrom` to anon. diff --git a/contracts/vault/offchainAsset/OffchainAssetReceiptVault.sol b/contracts/vault/offchainAsset/OffchainAssetReceiptVault.sol index 9cdd6ba4..f34827ca 100644 --- a/contracts/vault/offchainAsset/OffchainAssetReceiptVault.sol +++ b/contracts/vault/offchainAsset/OffchainAssetReceiptVault.sol @@ -446,6 +446,7 @@ contract OffchainAssetReceiptVault is ReceiptVault, AccessControl { /// mint new ERC20 shares and also increase the held receipt amount 1:1. /// @param receiptInformation_ Forwarded to receipt mint and /// `receiptInformation`. + /// @return shares_ As per IERC4626 `deposit`. function redeposit( uint256 assets_, address receiver_, @@ -456,18 +457,15 @@ contract OffchainAssetReceiptVault is ReceiptVault, AccessControl { if (id_ > highwaterId) { revert InvalidId(id_); } - _deposit( + + uint256 shares_ = _calculateDeposit( assets_, - receiver_, - _calculateDeposit( - assets_, - _shareRatio(msg.sender, receiver_, id_, ShareAction.Mint), - 0 - ), - id_, - receiptInformation_ + _shareRatio(msg.sender, receiver_, id_, ShareAction.Mint), + 0 ); - return assets_; + + _deposit(assets_, receiver_, shares_, id_, receiptInformation_); + return shares_; } /// Exposes `ERC20Snapshot` from Open Zeppelin behind a role restricted call. @@ -637,11 +635,14 @@ contract OffchainAssetReceiptVault is ReceiptVault, AccessControl { return; } - // Minting and burning is always allowed as it is controlled via. RBAC - // separately to the tier contracts. Minting and burning is ALSO valid - // after the certification expires as it is likely the only way to + // Minting and burning is always allowed for the respective roles if they + // interact directly with the shares/receipt. Minting and burning is ALSO + // valid after the certification expires as it is likely the only way to // repair the system and bring it back to a certifiable state. - if (from_ == address(0) || to_ == address(0)) { + if ( + (from_ == address(0) && hasRole(DEPOSITOR, to_)) || + (to_ == address(0) && hasRole(WITHDRAWER, from_)) + ) { return; } @@ -665,23 +666,28 @@ contract OffchainAssetReceiptVault is ReceiptVault, AccessControl { // If there is a tier contract we enforce it. if (address(tier_) != address(0) && minimumTier_ > 0) { - // The sender must have a valid tier. - uint256 fromReportTime_ = tier_.reportTimeForTier( - from_, - minimumTier_, - tierContext_ - ); - if (block.timestamp < fromReportTime_) { - revert UnauthorizedSenderTier(from_, fromReportTime_); + if (from_ != address(0)) { + // The sender must have a valid tier. + uint256 fromReportTime_ = tier_.reportTimeForTier( + from_, + minimumTier_, + tierContext_ + ); + if (block.timestamp < fromReportTime_) { + revert UnauthorizedSenderTier(from_, fromReportTime_); + } } - // The recipient must have a valid tier. - uint256 toReportTime_ = tier_.reportTimeForTier( - to_, - minimumTier_, - tierContext_ - ); - if (block.timestamp < toReportTime_) { - revert UnauthorizedRecipientTier(to_, toReportTime_); + + if (to_ != address(0)) { + // The recipient must have a valid tier. + uint256 toReportTime_ = tier_.reportTimeForTier( + to_, + minimumTier_, + tierContext_ + ); + if (block.timestamp < toReportTime_) { + revert UnauthorizedRecipientTier(to_, toReportTime_); + } } } } diff --git a/contracts/vault/receipt/IReceiptV1.sol b/contracts/vault/receipt/IReceiptV1.sol index 4be3d18d..94f77790 100644 --- a/contracts/vault/receipt/IReceiptV1.sol +++ b/contracts/vault/receipt/IReceiptV1.sol @@ -40,12 +40,15 @@ interface IReceiptV1 is IERC1155 { /// The owner MAY directly mint receipts for any account, ID and amount /// without restriction. The data MUST be treated as both ERC1155 data and /// receipt information. Overflow MUST revert as usual for ERC1155. - /// MUST REVERT if the `msg.sender` is NOT the owner. + /// MUST REVERT if the `msg.sender` is NOT the owner. Receipt information + /// MUST be emitted under the sender not the receiver account. + /// @param sender The sender to emit receipt information under. /// @param account The account to mint a receipt for. /// @param id The receipt ID to mint. /// @param amount The amount to mint for the `id`. /// @param data The ERC1155 data. MUST be emitted as receipt information. function ownerMint( + address sender, address account, uint256 id, uint256 amount, @@ -54,12 +57,15 @@ interface IReceiptV1 is IERC1155 { /// The owner MAY directly burn receipts for any account, ID and amount /// without restriction. Underflow MUST revert as usual for ERC1155. - /// MUST REVERT if the `msg.sender` is NOT the owner. + /// MUST REVERT if the `msg.sender` is NOT the owner. Receipt information + /// MUST be emitted under the sender not the receipt owner account. + /// @param sender The sender to emit receipt information under. /// @param account The account to burn a receipt for. /// @param id The receipt ID to burn. /// @param amount The amount to mint for the `id`. /// @param data MUST be emitted as receipt information. function ownerBurn( + address sender, address account, uint256 id, uint256 amount, diff --git a/contracts/vault/receipt/Receipt.sol b/contracts/vault/receipt/Receipt.sol index 2fc91a19..5391e0b2 100644 --- a/contracts/vault/receipt/Receipt.sol +++ b/contracts/vault/receipt/Receipt.sol @@ -41,23 +41,25 @@ contract Receipt is IReceiptV1, Ownable, ERC1155 { /// @inheritdoc IReceiptV1 function ownerMint( + address sender_, address account_, uint256 id_, uint256 amount_, bytes memory data_ ) external virtual onlyOwner { - _receiptInformation(account_, id_, data_); + _receiptInformation(sender_, id_, data_); _mint(account_, id_, amount_, data_); } /// @inheritdoc IReceiptV1 function ownerBurn( + address sender_, address account_, uint256 id_, uint256 amount_, bytes memory data_ ) external virtual onlyOwner { - _receiptInformation(account_, id_, data_); + _receiptInformation(sender_, id_, data_); _burn(account_, id_, amount_); } diff --git a/contracts/vault/receipt/ReceiptVault.sol b/contracts/vault/receipt/ReceiptVault.sol index 951db530..728aec12 100644 --- a/contracts/vault/receipt/ReceiptVault.sol +++ b/contracts/vault/receipt/ReceiptVault.sol @@ -547,16 +547,10 @@ contract ReceiptVault is bytes memory receiptInformation_ ) public returns (uint256) { uint256 id_ = _nextId(); - uint256 shareRatio_ = _shareRatio( - msg.sender, - receiver_, - id_, - ShareAction.Mint - ); uint256 shares_ = _calculateDeposit( assets_, - shareRatio_, + _shareRatio(msg.sender, receiver_, id_, ShareAction.Mint), depositMinShareRatio_ ); @@ -615,7 +609,13 @@ contract ReceiptVault is // erc1155 mint. // Receiving contracts MUST implement `IERC1155Receiver`. - _receipt.ownerMint(receiver_, id_, shares_, receiptInformation_); + _receipt.ownerMint( + msg.sender, + receiver_, + id_, + shares_, + receiptInformation_ + ); } /// Hook for additional actions that MUST complete or revert before deposit @@ -856,7 +856,13 @@ contract ReceiptVault is _burn(owner_, shares_); // ERC1155 burn. - _receipt.ownerBurn(owner_, id_, shares_, receiptInformation_); + _receipt.ownerBurn( + msg.sender, + owner_, + id_, + shares_, + receiptInformation_ + ); // Hook to allow additional withdrawal checks. _afterWithdraw(assets_, receiver_, owner_, shares_, id_); diff --git a/test/IPFSPull.test.ts b/test/IPFSPull.test.ts index f6017a40..ea73cdc6 100644 --- a/test/IPFSPull.test.ts +++ b/test/IPFSPull.test.ts @@ -7,7 +7,7 @@ describe("IPFS pull", async function () { const resp = await fetch( "https://ipfs.io/ipfs/bafkreih7cvpjocgrk7mgdel2hvjpquc26j4jo2jkez5y2qdaojfil7vley" ); - const ipfsData = await resp.json(); + const ipfsData = await resp.json().catch(console.error); assert( ipfsData.name === erc1155Metadata.name, diff --git a/test/offchainAsset/OffChainAssetReceiptVault.test.ts b/test/offchainAsset/OffChainAssetReceiptVault.test.ts index 0436f115..12d64493 100644 --- a/test/offchainAsset/OffChainAssetReceiptVault.test.ts +++ b/test/offchainAsset/OffChainAssetReceiptVault.test.ts @@ -25,6 +25,7 @@ import { deployOffchainAssetReceiptVaultFactory, } from "./deployOffchainAssetReceiptVault"; import { DepositWithReceiptEvent } from "../../typechain-types/contracts/vault/receipt/ReceiptVault"; +import { ReceiptInformationEvent } from "../../typechain-types/contracts/vault/receipt/Receipt"; const assert = require("assert"); @@ -201,6 +202,17 @@ describe("OffChainAssetReceiptVault", async function () { .connect(alice) .grantRole(await vault.connect(alice).DEPOSITOR(), alice.address); + const blockNum = await ethers.provider.getBlockNumber(); + const block = await ethers.provider.getBlock(blockNum); + const _until = block.timestamp + 100; + const _referenceBlockNumber = block.number; + + await vault + .connect(alice) + .grantRole(await vault.connect(alice).CERTIFIER(), bob.address); + + await vault.connect(bob).certify(_until, _referenceBlockNumber, false, []); + await vault .connect(alice) ["deposit(uint256,address,uint256,bytes)"]( @@ -1076,12 +1088,18 @@ describe("OffChainAssetReceiptVault", async function () { `wrong confiscated expected ${shares} got ${confiscated}` ); }); + it("Checks confiscated is transferred", async function () { const signers = await ethers.getSigners(); const alice = signers[0]; const bob = signers[1]; const [vault] = await deployOffChainAssetReceiptVault(); + const blockNum = await ethers.provider.getBlockNumber(); + const block = await ethers.provider.getBlock(blockNum); + const _certifiedUntil = block.timestamp + 100; + const _referenceBlockNumber = block.number; + const testErc20 = await ethers.getContractFactory("TestErc20"); const asset = (await testErc20.deploy()) as TestErc20; await asset.deployed(); @@ -1101,6 +1119,13 @@ describe("OffChainAssetReceiptVault", async function () { .connect(alice) .increaseAllowance(vault.connect(alice).address, assets); + await vault + .connect(alice) + .grantRole(await vault.connect(alice).CERTIFIER(), alice.address); + await vault + .connect(alice) + .certify(_certifiedUntil, _referenceBlockNumber, false, []); + await vault .connect(alice) ["deposit(uint256,address,uint256,bytes)"](assets, bob.address, ONE, []); @@ -1122,6 +1147,7 @@ describe("OffChainAssetReceiptVault", async function () { `Shares has not been confiscated` ); }); + it("Checks confiscated is same as receipt balance", async function () { const signers = await ethers.getSigners(); const [vault, receipt] = await deployOffChainAssetReceiptVault(); @@ -1528,4 +1554,55 @@ describe("OffChainAssetReceiptVault", async function () { "Failed to mint" ); }); + it("Check the receipt info sender when depositor mints for a different receiver", async () => { + const signers = await ethers.getSigners(); + const alice = signers[0]; + const bob = signers[1]; + + const [vault, receipt] = await deployOffChainAssetReceiptVault(); + + const testErc20 = await ethers.getContractFactory("TestErc20"); + const asset = (await testErc20.deploy()) as TestErc20; + await asset.deployed(); + + const aliceAmount = ethers.BigNumber.from(5000); + await asset.transfer(alice.address, aliceAmount); + await asset.connect(alice).increaseAllowance(vault.address, aliceAmount); + + const expectedId = 1; + + const informationBytes = [125, 126]; + await vault + .connect(alice) + .grantRole(await vault.connect(alice).DEPOSITOR(), alice.address); + + const blockNum = await ethers.provider.getBlockNumber(); + const block = await ethers.provider.getBlock(blockNum); + const _until = block.timestamp + 100; + const _referenceBlockNumber = block.number; + + await vault + .connect(alice) + .grantRole(await vault.connect(alice).CERTIFIER(), bob.address); + + await vault.connect(bob).certify(_until, _referenceBlockNumber, false, []); + + const { sender } = (await getEventArgs( + await vault + .connect(alice) + ["deposit(uint256,address,uint256,bytes)"]( + aliceAmount, + bob.address, + expectedId, + informationBytes + ), + "ReceiptInformation", + receipt + )) as ReceiptInformationEvent["args"]; + + assert( + sender === alice.address, + `wrong receipt information sender ${alice.address} got ${sender}` + ); + }); }); diff --git a/test/offchainAsset/Roles.test.ts b/test/offchainAsset/Roles.test.ts index 2f938842..2569f79a 100644 --- a/test/offchainAsset/Roles.test.ts +++ b/test/offchainAsset/Roles.test.ts @@ -139,6 +139,17 @@ describe("OffChainAssetReceiptVault Roles", async function () { .connect(alice) .grantRole(await vault.connect(alice).DEPOSITOR(), alice.address); + const blockNum = await ethers.provider.getBlockNumber(); + const block = await ethers.provider.getBlock(blockNum); + const _until = block.timestamp + 100; + const _referenceBlockNumber = block.number; + + await vault + .connect(alice) + .grantRole(await vault.connect(alice).CERTIFIER(), bob.address); + + await vault.connect(bob).certify(_until, _referenceBlockNumber, false, []); + await vault .connect(alice) ["deposit(uint256,address,uint256,bytes)"](