Skip to content

Commit

Permalink
Merge pull request #8 from superform-xyz/SUP-4982
Browse files Browse the repository at this point in the history
chore: add ability to transmute to receiver
  • Loading branch information
vikramarun authored Dec 24, 2023
2 parents c900467 + 39d9b19 commit 932abbf
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 50 deletions.
34 changes: 18 additions & 16 deletions src/ERC1155A.sol
Original file line number Diff line number Diff line change
Expand Up @@ -279,42 +279,43 @@ abstract contract ERC1155A is IERC1155A, IERC1155Errors {
// --------------------

/// @inheritdoc IERC1155A
function transmuteToERC20(address owner, uint256 id, uint256 amount) external override {
if (owner == address(0)) revert ZERO_ADDRESS();
function transmuteToERC20(address owner, uint256 id, uint256 amount, address receiver) external override {
if (owner == address(0) || receiver == address(0)) revert ZERO_ADDRESS();
/// @dev an approval is needed to burn
_burn(owner, msg.sender, id, amount);

address aERC20Token = aErc20TokenId[id];
if (aERC20Token == address(0)) revert AERC20_NOT_REGISTERED();

IaERC20(aERC20Token).mint(owner, amount);
emit TransmutedToERC20(owner, id, amount);
IaERC20(aERC20Token).mint(receiver, amount);
emit TransmutedToERC20(owner, id, amount,receiver);
}

/// @inheritdoc IERC1155A
function transmuteToERC1155A(address owner, uint256 id, uint256 amount) external override {
if (owner == address(0)) revert ZERO_ADDRESS();
function transmuteToERC1155A(address owner, uint256 id, uint256 amount, address receiver) external override {
if (owner == address(0) || receiver == address(0)) revert ZERO_ADDRESS();

address aERC20Token = aErc20TokenId[id];
if (aERC20Token == address(0)) revert AERC20_NOT_REGISTERED();

/// @dev an approval is needed to burn
IaERC20(aERC20Token).burn(owner, msg.sender, amount);
_mint(owner, msg.sender, id, amount, EMPTY_BYTES);
_mint(receiver, msg.sender, id, amount, EMPTY_BYTES);

emit TransmutedToERC1155A(owner, id, amount);
emit TransmutedToERC1155A(owner, id, amount, receiver);
}

/// @inheritdoc IERC1155A
function transmuteBatchToERC20(
address owner,
uint256[] calldata ids,
uint256[] calldata amounts
uint256[] calldata amounts,
address receiver
)
external
override
{
if (owner == address(0)) revert ZERO_ADDRESS();
if (owner == address(0) || receiver == address(0)) revert ZERO_ADDRESS();

uint256 idsLength = ids.length; // Saves MLOADs.
if (idsLength != amounts.length) revert LENGTH_MISMATCH();
Expand All @@ -326,22 +327,23 @@ abstract contract ERC1155A is IERC1155A, IERC1155Errors {
address aERC20Token = aErc20TokenId[ids[i]];
if (aERC20Token == address(0)) revert AERC20_NOT_REGISTERED();

IaERC20(aERC20Token).mint(owner, amounts[i]);
IaERC20(aERC20Token).mint(receiver, amounts[i]);
}

emit TransmutedBatchToERC20(owner, ids, amounts);
emit TransmutedBatchToERC20(owner, ids, amounts,receiver);
}

/// @inheritdoc IERC1155A
function transmuteBatchToERC1155A(
address owner,
uint256[] calldata ids,
uint256[] calldata amounts
uint256[] calldata amounts,
address receiver
)
external
override
{
if (owner == address(0)) revert ZERO_ADDRESS();
if (owner == address(0) || receiver == address(0)) revert ZERO_ADDRESS();

uint256 idsLength = ids.length; // Saves MLOADs.
if (idsLength != amounts.length) revert LENGTH_MISMATCH();
Expand All @@ -359,9 +361,9 @@ abstract contract ERC1155A is IERC1155A, IERC1155Errors {
IaERC20(aERC20Token).burn(owner, msg.sender, amount);
}

_batchMint(owner, msg.sender, ids, amounts, EMPTY_BYTES);
_batchMint(receiver, msg.sender, ids, amounts, EMPTY_BYTES);

emit TransmutedBatchToERC1155A(owner, ids, amounts);
emit TransmutedBatchToERC1155A(owner, ids, amounts, receiver);
}

// aERC20 Registration
Expand Down
28 changes: 16 additions & 12 deletions src/interfaces/IERC1155A.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ interface IERC1155A is IERC1155 {
event ApprovalForOne(address indexed owner, address indexed spender, uint256 id, uint256 amount);

/// @notice event emitted when an ERC1155A id is transmuted to an aERC20
event TransmutedToERC20(address indexed user, uint256 id, uint256 amount);
event TransmutedToERC20(address indexed user, uint256 id, uint256 amount, address indexed receiver);

/// @notice event emitted when an aERC20 is transmuted to an ERC1155 id
event TransmutedToERC1155A(address indexed user, uint256 id, uint256 amount);
event TransmutedToERC1155A(address indexed user, uint256 id, uint256 amount, address indexed receiver);

/// @notice event emitted when multiple ERC1155A ids are transmuted to aERC20s
event TransmutedBatchToERC20(address indexed user, uint256[] ids, uint256[] amounts);
event TransmutedBatchToERC20(address indexed user, uint256[] ids, uint256[] amounts, address indexed receiver);

/// @notice event emitted when multiple aERC20s are transmuted to ERC1155A ids
event TransmutedBatchToERC1155A(address indexed user, uint256[] ids, uint256[] amounts);
event TransmutedBatchToERC1155A(address indexed user, uint256[] ids, uint256[] amounts, address indexed receiver);

//////////////////////////////////////////////////////////////
// ERRORS //
Expand Down Expand Up @@ -142,28 +142,32 @@ interface IERC1155A is IERC1155 {
external
returns (bool);

/// @param onBehalfOf address of the user on whose behalf this transmutation is happening
/// @param owner address of the user on whose behalf this transmutation is happening
/// @param id id of the ERC20s to transmute to aErc20
/// @param amount amount of the ERC20s to transmute to aErc20
function transmuteToERC20(address onBehalfOf, uint256 id, uint256 amount) external;
/// @param receiver address of the user to receive the aErc20 token
function transmuteToERC20(address owner, uint256 id, uint256 amount, address receiver) external;

/// @param onBehalfOf address of the user on whose behalf this transmutation is happening
/// @param owner address of the user on whose behalf this transmutation is happening
/// @param id id of the ERC20s to transmute to erc1155
/// @param amount amount of the ERC20s to transmute to erc1155
function transmuteToERC1155A(address onBehalfOf, uint256 id, uint256 amount) external;
/// @param receiver address of the user to receive the erc1155 token id
function transmuteToERC1155A(address owner, uint256 id, uint256 amount, address receiver) external;

/// @notice Use transmuteBatchToERC20 to transmute multiple ERC1155 ids into separate ERC20
/// Easier to transmute to 1155A than to transmute back to aErc20 because of ERC1155 beauty!
/// @param onBehalfOf address of the user on whose behalf this transmutation is happening
/// @param owner address of the user on whose behalf this transmutation is happening
/// @param ids ids of the ERC1155A to transmute
/// @param amounts amounts of the ERC1155A to transmute
function transmuteBatchToERC20(address onBehalfOf, uint256[] memory ids, uint256[] memory amounts) external;
/// @param receiver address of the user to receive the aErc20 tokens
function transmuteBatchToERC20(address owner, uint256[] memory ids, uint256[] memory amounts, address receiver) external;

/// @notice Use transmuteBatchToERC1155A to transmute multiple ERC20 ids into separate ERC1155
/// @param onBehalfOf address of the user on whose behalf this transmutation is happening
/// @param owner address of the user on whose behalf this transmutation is happening
/// @param ids ids of the ERC20 to transmute
/// @param amounts amounts of the ERC20 to transmute
function transmuteBatchToERC1155A(address onBehalfOf, uint256[] memory ids, uint256[] memory amounts) external;
/// @param receiver address of the user to receive the erc1155 token ids
function transmuteBatchToERC1155A(address owner, uint256[] memory ids, uint256[] memory amounts, address receiver) external;

/// @notice payable to allow any implementing cross-chain protocol to be paid for fees for relaying this action to
/// various chain
Expand Down
12 changes: 6 additions & 6 deletions src/test/ERC1155.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -481,22 +481,22 @@ contract ERC1155Test is DSTestPlus, ERC1155TokenReceiver {

function testFailZeroAddressTransmuteBatchToERC20() public {
/// zero address check on transmuteBatchToERC20
token.transmuteBatchToERC20(address(0), new uint256[](1), new uint256[](1));
token.transmuteBatchToERC20(address(0), new uint256[](1), new uint256[](1), address(0));
}

function testFailZeroAddressTransmuteBatchToERC1155A() public {
/// zero address check on transmuteBatchToERC1155A
token.transmuteBatchToERC1155A(address(0), new uint256[](1), new uint256[](1));
token.transmuteBatchToERC1155A(address(0), new uint256[](1), new uint256[](1), address(0));
}

function testFailZeroAddressTransmuteToERC20() public {
/// zero address check on transmuteToERC20
token.transmuteToERC20(address(0), 1, 100);
token.transmuteToERC20(address(0), 1, 100, address(0));
}

function testFailZeroAddressTransmuteToERC1155A() public {
/// zero address check on transmuteToERC1155A
token.transmuteToERC1155A(address(0), 1, 100);
token.transmuteToERC1155A(address(0), 1, 100, address(0));
}

function testFailArrayMismatchIncreaseAllowanceForMany() public {
Expand All @@ -512,11 +512,11 @@ contract ERC1155Test is DSTestPlus, ERC1155TokenReceiver {
}

function testFailArrayMismatchTransmuteBatchToERC20() public {
token.transmuteBatchToERC20(address(0xBEEF), new uint256[](1), new uint256[](2));
token.transmuteBatchToERC20(address(0xBEEF), new uint256[](1), new uint256[](2), address(0xBEEF));
}

function testFailArrayMismatchTransmuteBatchToERC1155A() public {
token.transmuteBatchToERC1155A(address(0xBEEF), new uint256[](1), new uint256[](2));
token.transmuteBatchToERC1155A(address(0xBEEF), new uint256[](1), new uint256[](2), address(0xBEEF));
}

function testFailMintToNonERC155Recipient() public {
Expand Down
67 changes: 51 additions & 16 deletions src/test/ERC1155_A.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ contract ERC1155ATest is Test {
vm.stopPrank();
vm.startPrank(alice);

MockedERC1155A.transmuteToERC20(alice, id, THOUSAND_E18);
MockedERC1155A.transmuteToERC20(alice, id, THOUSAND_E18, alice);
assertEq(MockedERC1155A.balanceOf(alice, id), 0);

uint256 aERC20Balance = aERC20Token.balanceOf(alice);
Expand All @@ -215,7 +215,7 @@ contract ERC1155ATest is Test {
aERC20Token.approve(address(MockedERC1155A), aERC20Balance);

/// NOTE: Test if 1:1 between 1155 and 20 always holds
MockedERC1155A.transmuteToERC1155A(alice, id, aERC20Balance);
MockedERC1155A.transmuteToERC1155A(alice, id, aERC20Balance, alice);

assertEq(MockedERC1155A.balanceOf(alice, id), THOUSAND_E18);

Expand All @@ -236,16 +236,16 @@ contract ERC1155ATest is Test {
vm.startPrank(alice);

vm.expectRevert(IERC1155A.AERC20_NOT_REGISTERED.selector);
MockedERC1155A.transmuteToERC20(alice, id, THOUSAND_E18);
MockedERC1155A.transmuteToERC20(alice, id, THOUSAND_E18, alice);

vm.expectRevert(IERC1155A.AERC20_NOT_REGISTERED.selector);
MockedERC1155A.transmuteToERC1155A(alice, id, THOUSAND_E18);
MockedERC1155A.transmuteToERC1155A(alice, id, THOUSAND_E18, alice);

vm.expectRevert(IERC1155A.AERC20_NOT_REGISTERED.selector);
MockedERC1155A.transmuteBatchToERC20(alice, ids, amounts);
MockedERC1155A.transmuteBatchToERC20(alice, ids, amounts, alice);

vm.expectRevert(IERC1155A.AERC20_NOT_REGISTERED.selector);
MockedERC1155A.transmuteBatchToERC1155A(alice, ids, amounts);
MockedERC1155A.transmuteBatchToERC1155A(alice, ids, amounts, alice);
}

function testAERC20CreationSingleApprove() public {
Expand All @@ -259,7 +259,7 @@ contract ERC1155ATest is Test {
MockedERC1155A.setApprovalForOne(bob, id, THOUSAND_E18);

vm.prank(bob);
MockedERC1155A.transmuteToERC20(alice, id, THOUSAND_E18);
MockedERC1155A.transmuteToERC20(alice, id, THOUSAND_E18, alice);

assertEq(MockedERC1155A.balanceOf(alice, id), 0);
assertEq(MockedERC1155A.allowance(alice, bob, id), 0);
Expand All @@ -274,7 +274,7 @@ contract ERC1155ATest is Test {

vm.prank(bob);
/// NOTE: Test if 1:1 between 1155 and 20 always holds
MockedERC1155A.transmuteToERC1155A(alice, id, aERC20Balance);
MockedERC1155A.transmuteToERC1155A(alice, id, aERC20Balance, alice);

assertEq(MockedERC1155A.balanceOf(alice, id), THOUSAND_E18);

Expand All @@ -292,7 +292,7 @@ contract ERC1155ATest is Test {
MockedERC1155A.setApprovalForOne(bob, id, THOUSAND_E18);

vm.prank(bob);
MockedERC1155A.transmuteToERC20(alice, id, THOUSAND_E18);
MockedERC1155A.transmuteToERC20(alice, id, THOUSAND_E18, alice);

assertEq(MockedERC1155A.balanceOf(alice, id), 0);
assertEq(MockedERC1155A.allowance(alice, bob, id), 0);
Expand All @@ -306,7 +306,7 @@ contract ERC1155ATest is Test {

vm.prank(bob);
vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, bob, 0, aERC20Balance));
MockedERC1155A.transmuteToERC1155A(alice, id, aERC20Balance);
MockedERC1155A.transmuteToERC1155A(alice, id, aERC20Balance, alice);
}

function testAERC20CreationBatch() public {
Expand All @@ -328,7 +328,7 @@ contract ERC1155ATest is Test {
amounts[0] = THOUSAND_E18;
amounts[1] = THOUSAND_E18;

MockedERC1155A.transmuteBatchToERC20(alice, ids, amounts);
MockedERC1155A.transmuteBatchToERC20(alice, ids, amounts, alice);

assertEq(MockedERC1155A.balanceOf(alice, ids[0]), 0);
assertEq(MockedERC1155A.balanceOf(alice, ids[1]), 0);
Expand All @@ -337,7 +337,7 @@ contract ERC1155ATest is Test {
assertEq(aERC20Token2.balanceOf(alice), THOUSAND_E18);

/// NOTE: Test if 1:1 between 1155 and 20 always holds
MockedERC1155A.transmuteBatchToERC1155A(alice, ids, amounts);
MockedERC1155A.transmuteBatchToERC1155A(alice, ids, amounts, alice);

assertEq(MockedERC1155A.balanceOf(alice, ids[0]), THOUSAND_E18);
assertEq(MockedERC1155A.balanceOf(alice, ids[1]), THOUSAND_E18);
Expand Down Expand Up @@ -370,7 +370,7 @@ contract ERC1155ATest is Test {

vm.prank(bob);

MockedERC1155A.transmuteBatchToERC20(alice, ids, amounts);
MockedERC1155A.transmuteBatchToERC20(alice, ids, amounts, alice);
vm.startPrank(alice);

assertEq(MockedERC1155A.balanceOf(alice, ids[0]), 0);
Expand All @@ -387,7 +387,7 @@ contract ERC1155ATest is Test {

vm.prank(bob);
/// NOTE: Test if 1:1 between 1155 and 20 always holds
MockedERC1155A.transmuteBatchToERC1155A(alice, ids, amounts);
MockedERC1155A.transmuteBatchToERC1155A(alice, ids, amounts, alice);

assertEq(MockedERC1155A.balanceOf(alice, ids[0]), THOUSAND_E18);
assertEq(MockedERC1155A.balanceOf(alice, ids[1]), THOUSAND_E18);
Expand Down Expand Up @@ -419,7 +419,7 @@ contract ERC1155ATest is Test {

vm.prank(bob);

MockedERC1155A.transmuteBatchToERC20(alice, ids, amounts);
MockedERC1155A.transmuteBatchToERC20(alice, ids, amounts, alice);
vm.startPrank(alice);

assertEq(MockedERC1155A.balanceOf(alice, ids[0]), 0);
Expand All @@ -434,7 +434,7 @@ contract ERC1155ATest is Test {
vm.prank(bob);
vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, bob, 0, THOUSAND_E18));
/// NOTE: Test if 1:1 between 1155 and 20 always holds
MockedERC1155A.transmuteBatchToERC1155A(alice, ids, amounts);
MockedERC1155A.transmuteBatchToERC1155A(alice, ids, amounts, alice);
}

function testAERC20CreationrAlreadyRegistered() public {
Expand All @@ -445,4 +445,39 @@ contract ERC1155ATest is Test {
vm.expectRevert(IERC1155A.AERC20_ALREADY_REGISTERED.selector);
aERC20Token = aERC20(MockedERC1155A.registerAERC20(1));
}

function testAERC20TransmuteRoundTripToReceiver() public {
vm.startPrank(deployer);
uint256 id = 3;
MockedERC1155A.mint(alice, id, THOUSAND_E18, "");
aERC20 aERC20Token = aERC20(MockedERC1155A.registerAERC20(id));
vm.stopPrank();
vm.startPrank(alice);

MockedERC1155A.transmuteToERC20(alice, id, THOUSAND_E18, bob);
assertEq(MockedERC1155A.balanceOf(alice, id), 0);
assertEq(MockedERC1155A.balanceOf(bob, id), 0);

uint256 aliceaERC20Balance = aERC20Token.balanceOf(alice);
assertEq(aliceaERC20Balance, 0);

uint256 bobaERC20Balance = aERC20Token.balanceOf(bob);
assertEq(bobaERC20Balance, THOUSAND_E18);

vm.stopPrank();

vm.startPrank(bob);

aERC20Token.approve(address(MockedERC1155A), bobaERC20Balance);

/// NOTE: Test if 1:1 between 1155 and 20 always holds
MockedERC1155A.transmuteToERC1155A(bob, id, bobaERC20Balance, alice);

assertEq(MockedERC1155A.balanceOf(alice, id), THOUSAND_E18);
assertEq(MockedERC1155A.balanceOf(bob, id), 0);

assertEq(aERC20Token.balanceOf(address(bob)), 0);
assertEq(aERC20Token.balanceOf(address(alice)), 0);
vm.stopPrank();
}
}

0 comments on commit 932abbf

Please sign in to comment.