Skip to content

Commit ff15c6e

Browse files
authored
feat: converting the ERC20 and ERC4626 into OApps (#1568)
Signed-off-by: shankar <shankar@layerzerolabs.org>
1 parent eaf2a65 commit ff15c6e

10 files changed

Lines changed: 624 additions & 14 deletions

File tree

packages/ovault-composer/contracts/ERC20MintBurn.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ contract ERC20MintBurn is IERC20MintBurnExtension, ERC20, Ownable {
1010
mapping(address => uint256) public approvedBurners;
1111
address public approvedSpender;
1212

13+
bool public constant ERC4626AdapterCompliant = true;
14+
1315
constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) Ownable(msg.sender) {}
1416

1517
function setMinter(address _minter, uint256 _amount) external onlyOwner {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
pragma solidity >=0.8.0;
3+
4+
import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol";
5+
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
6+
import { IERC20MintBurnExtension } from "./interfaces/IERC20MintBurnExtension.sol";
7+
8+
contract OFTMintBurn is OFT, IERC20MintBurnExtension {
9+
mapping(address => uint256) public approvedMinters;
10+
mapping(address => uint256) public approvedBurners;
11+
address public approvedSpender;
12+
13+
bool public constant ERC4626AdapterCompliant = true;
14+
15+
constructor(
16+
string memory _name,
17+
string memory _symbol,
18+
address _lzEndpoint,
19+
address _delegate
20+
) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}
21+
22+
function setMinter(address _minter, uint256 _amount) external onlyOwner {
23+
approvedMinters[_minter] = _amount;
24+
emit MinterSet(_minter, _amount);
25+
}
26+
27+
function setBurner(address _burner, uint256 _amount) external onlyOwner {
28+
approvedBurners[_burner] = _amount;
29+
emit BurnerSet(_burner, _amount);
30+
}
31+
32+
function setSpender(address _spender) external onlyOwner {
33+
approvedSpender = _spender;
34+
emit SpenderSet(_spender);
35+
}
36+
37+
function mint(address _to, uint256 _amount) external {
38+
require(approvedMinters[msg.sender] >= _amount, "ERC20MintBurn: minter not approved");
39+
_mint(_to, _amount);
40+
}
41+
42+
function burn(address _from, uint256 _amount) external {
43+
require(approvedBurners[msg.sender] >= _amount, "ERC20MintBurn: burner not approved");
44+
_burn(_from, _amount);
45+
}
46+
47+
function spendAllowance(address _owner, address _spender, uint256 _amount) external {
48+
require(msg.sender == approvedSpender, "ERC20MintBurn: spender not approved");
49+
_spendAllowance(_owner, _spender, _amount);
50+
}
51+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import { IOFT } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";
5+
import { IERC20MintBurnExtension } from "./interfaces/IERC20MintBurnExtension.sol";
6+
import { ERC4626Adapter } from "./ERC4626Adapter.sol";
7+
import { IOVault } from "./interfaces/IOVault.sol";
8+
9+
contract OVault is ERC4626Adapter, IOVault {
10+
constructor(IOFT _asset, IOFT _share) ERC4626Adapter(_asset.token(), _share.token()) {
11+
if (!IERC20MintBurnExtension(_share.token()).ERC4626AdapterCompliant()) {
12+
revert ShareNotERC4626AdapterCompliant();
13+
}
14+
}
15+
}

packages/ovault-composer/contracts/interfaces/IERC20MintBurnExtension.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ interface IERC20MintBurnExtension {
66
event BurnerSet(address burner, uint256 amount);
77
event SpenderSet(address spender);
88

9+
error CanNotMintAmount(address minter, uint256 amount);
10+
error CanNotBurnAmount(address burner, uint256 amount);
11+
error CanNotSpend(address spender);
12+
913
function approvedMinters(address minter) external view returns (uint256);
1014
function approvedBurners(address burner) external view returns (uint256);
1115
function approvedSpender() external view returns (address);
@@ -17,4 +21,6 @@ interface IERC20MintBurnExtension {
1721
function mint(address to, uint256 amount) external;
1822
function burn(address from, uint256 amount) external;
1923
function spendAllowance(address owner, address spender, uint256 amount) external;
24+
25+
function ERC4626AdapterCompliant() external view returns (bool);
2026
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
interface IOVault {
5+
error ShareNotERC4626AdapterCompliant();
6+
}

packages/ovault-composer/test/ERC44626.t.sol

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ contract ERC4626AdapterTest is Test {
3434
share.setSpender(address(vault));
3535
}
3636

37-
function test_invariantMetadata() public view {
37+
function test_erc4626_invariantMetadata() public view {
3838
assertEq(vault.name(), SHARE_NAME);
3939
assertEq(vault.symbol(), SHARE_SYMBOL);
4040
assertEq(vault.decimals(), 18);
4141
}
4242

43-
function test_Metadata(string calldata name, string calldata symbol) public {
43+
function test_erc4626_Metadata(string calldata name, string calldata symbol) public {
4444
address shareAddress = address(new MockERC20MintBurn(name, symbol));
4545
MockERC4626Adapter vlt = new MockERC4626Adapter(address(asset), shareAddress);
4646
assertEq(vlt.name(), name);
@@ -49,7 +49,7 @@ contract ERC4626AdapterTest is Test {
4949
assertEq(address(vlt.share()), shareAddress);
5050
}
5151

52-
function testFuzz_SingleDepositWithdraw(uint128 amount) public {
52+
function testFuzz_erc4626_SingleDepositWithdraw(uint128 amount) public {
5353
if (amount == 0) amount = 1;
5454

5555
uint256 aliceassetAmount = amount;
@@ -93,7 +93,7 @@ contract ERC4626AdapterTest is Test {
9393
assertEq(asset.balanceOf(alice), alicePreDepositBal);
9494
}
9595

96-
function testFuzz_SingleMintRedeem(uint128 amount) public {
96+
function testFuzz_erc4626_SingleMintRedeem(uint128 amount) public {
9797
if (amount == 0) amount = 1;
9898

9999
uint256 aliceShareAmount = amount;
@@ -130,7 +130,7 @@ contract ERC4626AdapterTest is Test {
130130
assertEq(asset.balanceOf(alice), alicePreDepositBal);
131131
}
132132

133-
function testMultipleMintDepositRedeemWithdraw() public {
133+
function test_erc4626_MultipleMintDepositRedeemWithdraw() public {
134134
// Scenario:
135135
// A = Alice, B = Bob
136136
// ________________________________________________________
@@ -346,7 +346,7 @@ contract ERC4626AdapterTest is Test {
346346
assertEq(asset.balanceOf(address(vault)), 0);
347347
}
348348

349-
function test_FailDepositWithNotEnoughApproval() public {
349+
function test_erc4626_FailDepositWithNotEnoughApproval() public {
350350
asset.mint(address(this), 0.5e18);
351351
asset.approve(address(vault), 0.5e18);
352352
assertEq(asset.allowance(address(this), address(vault)), 0.5e18);
@@ -357,7 +357,7 @@ contract ERC4626AdapterTest is Test {
357357
vault.deposit(1e18, address(this));
358358
}
359359

360-
function test_FailWithdrawExceedsMaxWithdraw() public {
360+
function test_erc4626_FailWithdrawExceedsMaxWithdraw() public {
361361
asset.mint(address(this), 0.5e18);
362362
asset.approve(address(vault), 0.5e18);
363363

@@ -369,7 +369,7 @@ contract ERC4626AdapterTest is Test {
369369
vault.withdraw(1e18, address(this), address(this));
370370
}
371371

372-
function test_FailRedeemWithNotEnoughShareAmount() public {
372+
function test_erc4626_FailRedeemWithNotEnoughShareAmount() public {
373373
asset.mint(address(this), 0.5e18);
374374
asset.approve(address(vault), 0.5e18);
375375

@@ -381,35 +381,35 @@ contract ERC4626AdapterTest is Test {
381381
vault.redeem(1e18, address(this), address(this));
382382
}
383383

384-
function test_FailWithdrawWithNoassetAmount() public {
384+
function test_erc4626_FailWithdrawWithNoassetAmount() public {
385385
vm.expectRevert(
386386
abi.encodeWithSelector(IERC4626Adapter.ERC4626ExceededMaxWithdraw.selector, address(this), 1e18, 0)
387387
);
388388
vault.withdraw(1e18, address(this), address(this));
389389
}
390390

391-
function test_FailRedeemWithNoShareAmount() public {
391+
function test_erc4626_FailRedeemWithNoShareAmount() public {
392392
vm.expectRevert(
393393
abi.encodeWithSelector(IERC4626Adapter.ERC4626ExceededMaxRedeem.selector, address(this), 1e18, 0)
394394
);
395395
vault.redeem(1e18, address(this), address(this));
396396
}
397397

398-
function test_FailDepositWithNoApproval() public {
398+
function test_erc4626_FailDepositWithNoApproval() public {
399399
vm.expectRevert(
400400
abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, address(vault), 0, 1e18)
401401
);
402402
vault.deposit(1e18, address(this));
403403
}
404404

405-
function test_FailMintWithNoApproval() public {
405+
function test_erc4626_FailMintWithNoApproval() public {
406406
vm.expectRevert(
407407
abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, address(vault), 0, 1e18)
408408
);
409409
vault.mint(1e18, address(this));
410410
}
411411

412-
function test_MintZero() public {
412+
function test_erc4626_MintZero() public {
413413
vault.mint(0, address(this));
414414

415415
assertEq(vault.balanceOf(address(this)), 0);
@@ -418,7 +418,7 @@ contract ERC4626AdapterTest is Test {
418418
assertEq(vault.totalAssets(), 0);
419419
}
420420

421-
function test_WithdrawZero() public {
421+
function test_erc4626_WithdrawZero() public {
422422
vault.withdraw(0, address(this), address(this));
423423

424424
assertEq(vault.balanceOf(address(this)), 0);

0 commit comments

Comments
 (0)