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

Refactor #10

Merged
merged 4 commits into from
Jun 12, 2024
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
5 changes: 3 additions & 2 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ pragma solidity ^0.8.13;

import {Script} from "forge-std/Script.sol";
import {Memberlist} from "src/Memberlist.sol";
import {PermissionedERC20Wrapper, IERC20Metadata} from "src/PermissionedERC20Wrapper.sol";
import {PermissionedERC20Wrapper} from "src/PermissionedERC20Wrapper.sol";
import {ERC20PermissionedBase, IERC20} from "lib/erc20-permissioned/src/ERC20PermissionedBase.sol";

contract DeployScript is Script {
address USDC = vm.envAddress("UNDERLYING_TOKEN");
Expand All @@ -21,7 +22,7 @@ contract DeployScript is Script {
PermissionedERC20Wrapper wrappedUSDC = new PermissionedERC20Wrapper(
"Attested USDC",
"aUSDC",
IERC20Metadata(USDC),
IERC20(USDC),
morpho,
bundler,
attestationService,
Expand Down
67 changes: 6 additions & 61 deletions src/PermissionedERC20Wrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ pragma solidity ^0.8.20;

import {Memberlist} from "src/Memberlist.sol";
import {Auth} from "lib/liquidity-pools/src/Auth.sol";
import {ERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {ERC20Wrapper} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
import {ERC20Permit} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20PermissionedBase, IERC20} from "lib/erc20-permissioned/src/ERC20PermissionedBase.sol";
import {IERC20Metadata} from "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";

interface IAttestationService {
Expand Down Expand Up @@ -37,42 +34,29 @@ struct Attestation {
/// VERIFIED_COUNTRY attestation of anything other than "US". Attestations are provided by Coinbase
/// through the Ethereum Attestation Service.
/// @author Modified from OpenZeppelin Contracts v5.0.0 (token/ERC20/extensions/ERC20Wrapper.sol)
contract PermissionedERC20Wrapper is Auth, ERC20, ERC20Wrapper, ERC20Permit {
contract PermissionedERC20Wrapper is Auth, ERC20PermissionedBase {
bytes32 public constant verifiedCountrySchemaUid =
0x1801901fabd0e6189356b4fb52bb0ab855276d84f7ec140839fbd1f6801ca065;
bytes32 public constant verifiedAccountSchemaUid =
0xf8b05c79f090979bf4a80270aba232dff11a10d9ca55c4f88de95317970f0de9;

/// @notice The address of the Morpho contract.
address public immutable MORPHO;

/// @notice The address of the Bundler contract.
address public immutable BUNDLER;

/// @notice The underlying token.
IERC20Metadata public immutable _underlying;

Memberlist public memberlist;
IAttestationService public attestationService;
IAttestationIndexer public attestationIndexer;

constructor(
string memory name_,
string memory symbol_,
IERC20Metadata underlyingToken_,
IERC20 underlyingToken_,
address morpho_,
address bundler_,
address attestationService_,
address attestationIndexer_,
address memberlist_
) ERC20Wrapper(underlyingToken_) ERC20Permit(name_) ERC20(name_, symbol_) {
MORPHO = morpho_;
BUNDLER = bundler_;
) ERC20PermissionedBase(name_, symbol_, underlyingToken_, morpho_, bundler_) {
attestationService = IAttestationService(attestationService_);
attestationIndexer = IAttestationIndexer(attestationIndexer_);
memberlist = Memberlist(memberlist_);
if (address(underlyingToken_) == address(this)) revert ERC20InvalidUnderlying(address(this));
_underlying = underlyingToken_;

wards[msg.sender] = 1;
emit Rely(msg.sender);
Expand All @@ -86,48 +70,9 @@ contract PermissionedERC20Wrapper is Auth, ERC20, ERC20Wrapper, ERC20Permit {
else revert("PermissionedERC20Wrapper/file-unrecognized-param");
}

// --- ERC20 wrapping ---
/**
* @dev Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens.
*/
function depositFor(address account, uint256 value) public override returns (bool) {
address sender = _msgSender();
if (sender == address(this)) revert ERC20InvalidSender(address(this));
if (account == address(this)) revert ERC20InvalidReceiver(account);
SafeERC20.safeTransferFrom(_underlying, sender, address(this), value);
_mint(account, value);
return true;
}

/**
* @dev Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens.
*/
function withdrawTo(address account, uint256 value) public override returns (bool) {
if (account == address(this)) revert ERC20InvalidReceiver(account);
_burn(_msgSender(), value);
SafeERC20.safeTransfer(_underlying, account, value);
return true;
}

function _update(address from, address to, uint256 value) internal virtual override {
require(hasPermission(to), "PermissionedERC20Wrapper/no-permission");
super._update(from, to, value);
}

function decimals() public view virtual override(ERC20, ERC20Wrapper) returns (uint8) {
try IERC20Metadata(address(_underlying)).decimals() returns (uint8 value) {
return value;
} catch {
return super.decimals();
}
}

// --- Permission checks ---
function hasPermission(address account) public view returns (bool attested) {
if (
account == address(this) || account == address(0) || account == MORPHO || account == BUNDLER
|| memberlist.isMember(account)
) {
function hasPermission(address account) public view override returns (bool attested) {
if (super.hasPermission(account) || memberlist.isMember(account)) {
return true;
}

Expand Down
39 changes: 6 additions & 33 deletions test/unit/PermissionedERC20Wrapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ pragma solidity ^0.8.13;

import {ERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import {Memberlist} from "src/Memberlist.sol";
import {PermissionedERC20Wrapper, IERC20Metadata} from "src/PermissionedERC20Wrapper.sol";
import {PermissionedERC20Wrapper} from "src/PermissionedERC20Wrapper.sol";
import {ERC20PermissionedBase, IERC20} from "lib/erc20-permissioned/src/ERC20PermissionedBase.sol";
import {Test} from "forge-std/Test.sol";
import {IERC20} from "lib/erc20-permissioned/src/ERC20PermissionedBase.sol";

contract SimpleERC20 is ERC20 {
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {}
Expand All @@ -28,7 +30,7 @@ contract PermissionedERC20WrapperTest is Test {
wrappedUSDC = new PermissionedERC20Wrapper(
"attested USDC",
"aUSDC",
IERC20Metadata(address(usdc)),
IERC20(address(usdc)),
address(0),
address(0),
address(0x4200000000000000000000000000000000000021),
Expand Down Expand Up @@ -116,7 +118,7 @@ contract PermissionedERC20WrapperTest is Test {
deal(address(usdc), userUS, 100);
vm.startPrank(userUS);
usdc.approve(address(wrappedUSDC), 100);
vm.expectRevert("PermissionedERC20Wrapper/no-permission");
vm.expectRevert(abi.encodeWithSelector(ERC20PermissionedBase.NoPermission.selector, userUS));
wrappedUSDC.depositFor(userUS, 100);
vm.stopPrank();
}
Expand All @@ -142,28 +144,6 @@ contract PermissionedERC20WrapperTest is Test {
assertEq(usdc.balanceOf(userNonUS1), 100);
}

function test_WithdrawTo_WithNonPermissioned_Works() public {
deal(address(wrappedUSDC), userUS, 100);
deal(address(usdc), address(wrappedUSDC), 100);
vm.startPrank(userUS);
wrappedUSDC.approve(address(wrappedUSDC), 100);
wrappedUSDC.withdrawTo(userUS, 100);
vm.stopPrank();
assertEq(wrappedUSDC.balanceOf(userUS), 0);
assertEq(usdc.balanceOf(userUS), 100);
}

function test_WithdrawTo_FromNonPermissionedToPermissioned_Works() public {
deal(address(wrappedUSDC), userUS, 100);
deal(address(usdc), address(wrappedUSDC), 100);
vm.startPrank(userUS);
wrappedUSDC.approve(address(wrappedUSDC), 100);
wrappedUSDC.withdrawTo(userNonUS1, 100);
vm.stopPrank();
assertEq(wrappedUSDC.balanceOf(userNonUS1), 0);
assertEq(usdc.balanceOf(userNonUS1), 100);
}

function test_transfer_FromPermissionedToPermissioned_Works() public {
deal(address(wrappedUSDC), userNonUS1, 100);
vm.prank(userNonUS1);
Expand All @@ -182,15 +162,8 @@ contract PermissionedERC20WrapperTest is Test {

function test_transfer_FromPermissionedToNonPermissioned_Fails() public {
deal(address(wrappedUSDC), userNonUS1, 100);
vm.expectRevert("PermissionedERC20Wrapper/no-permission");
vm.expectRevert(abi.encodeWithSelector(ERC20PermissionedBase.NoPermission.selector, userUS));
vm.prank(userNonUS1);
wrappedUSDC.transfer(userUS, 100);
}

function test_transfer_FromNonPermissionedToPermissioned_Works() public {
deal(address(wrappedUSDC), userUS, 100);
vm.prank(userUS);
wrappedUSDC.transfer(userNonUS1, 100);
assertEq(wrappedUSDC.balanceOf(userNonUS1), 100);
}
}
Loading