Skip to content

Commit 359a4ac

Browse files
authored
Merge pull request #39 from webb-tools/drew-native-token-wrapper
Add ability to wrap native assets into the token wrapper
2 parents d30b84a + ac6890a commit 359a4ac

File tree

15 files changed

+36575
-714
lines changed

15 files changed

+36575
-714
lines changed

contracts/anchors/bridged/Anchor.sol

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,34 @@ contract Anchor is LinkableAnchor {
2929
token = address(_token);
3030
}
3131

32-
function wrap(address tokenAddress, uint256 amount) public {
32+
function wrapToken(address tokenAddress, uint256 amount) public {
3333
ITokenWrapper(token).wrapFor(msg.sender, tokenAddress, amount);
3434
}
3535

36-
function unwrap(address tokenAddress, uint256 amount) public {
36+
function unwrapIntoToken(address tokenAddress, uint256 amount) public {
37+
ITokenWrapper(token).unwrapFor(msg.sender, tokenAddress, amount);
38+
}
39+
40+
function wrapNative() payable public {
41+
ITokenWrapper(token).wrapFor{value: msg.value}(msg.sender, address(0), 0);
42+
}
43+
44+
function unwrapIntoNative(address tokenAddress, uint256 amount) public {
3745
ITokenWrapper(token).unwrapFor(msg.sender, tokenAddress, amount);
3846
}
3947

4048
function wrapAndDeposit(
4149
address tokenAddress,
4250
bytes32 _commitment
43-
) public {
51+
) payable public {
4452
require(!commitments[_commitment], "The commitment has been submitted");
45-
// wrap the token and send directly to this contract
46-
ITokenWrapper(token).wrapForAndSendTo(msg.sender, tokenAddress, denomination, address(this));
53+
// wrap into the token and send directly to this contract
54+
ITokenWrapper(token).wrapForAndSendTo{value: msg.value}(
55+
msg.sender,
56+
tokenAddress,
57+
denomination,
58+
address(this)
59+
);
4760
// insert a new commitment to the tree
4861
uint32 insertedIndex = _insert(_commitment);
4962
commitments[_commitment] = true;

contracts/interfaces/ITokenWrapper.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ pragma solidity ^0.8.0;
1010
@author Webb Technologies.
1111
*/
1212
interface ITokenWrapper {
13-
function wrap(address tokenAddress, uint256 amount) external;
13+
function wrap(address tokenAddress, uint256 amount) payable external;
1414
function unwrap(address tokenAddress, uint256 amount) external;
15-
function wrapFor(address sender, address tokenAddress, uint256 amount) external;
16-
function wrapForAndSendTo(address sender, address tokenAddress, uint256 amount, address mintRecipient) external;
15+
function wrapFor(address sender, address tokenAddress, uint256 amount) payable external;
16+
function wrapForAndSendTo(address sender, address tokenAddress, uint256 amount, address mintRecipient) payable external;
1717
function unwrapFor(address sender, address tokenAddress, uint256 amount) external;
1818
}

contracts/mocks/GTokenWrapperMock.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ contract GTokenWrapperMock is GovernedTokenWrapper {
1313
* @notice Construct a new Comp token
1414
*/
1515
constructor(string memory name, string memory symbol, address governor, uint256 limit)
16-
GovernedTokenWrapper(name, symbol, governor, limit) {}
16+
GovernedTokenWrapper(name, symbol, governor, limit, true) {}
1717
}

contracts/tokens/GovernedTokenWrapper.sol

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,21 @@ contract GovernedTokenWrapper is TokenWrapper {
1717
address[] public tokens;
1818
mapping (address => bool) valid;
1919

20+
bool isNativeAllowed;
2021
uint256 public wrappingLimit;
2122

22-
constructor(string memory name, string memory symbol, address _governor, uint256 _limit) TokenWrapper(name, symbol) {
23+
constructor(string memory name, string memory symbol, address _governor, uint256 _limit, bool _isNativeAllowed) TokenWrapper(name, symbol) {
2324
governor = _governor;
2425
wrappingLimit = _limit;
26+
isNativeAllowed = _isNativeAllowed;
2527
}
2628

2729
function setGovernor(address _governor) public onlyGovernor {
2830
governor = _governor;
2931
}
3032

31-
function _isValidAddress(address tokenAddress) override internal virtual returns (bool) {
32-
return valid[tokenAddress];
33-
}
34-
35-
function _isValidAmount(uint256 amount) override internal virtual returns (bool) {
36-
return amount + this.totalSupply() <= wrappingLimit;
33+
function setNativeAllowed(bool _isNativeAllowed) public onlyGovernor {
34+
isNativeAllowed = _isNativeAllowed;
3735
}
3836

3937
function add(address tokenAddress) public onlyGovernor {
@@ -46,6 +44,18 @@ contract GovernedTokenWrapper is TokenWrapper {
4644
wrappingLimit = limit;
4745
}
4846

47+
function _isValidAddress(address tokenAddress) override internal virtual returns (bool) {
48+
return valid[tokenAddress];
49+
}
50+
51+
function _isValidAmount(uint256 amount) override internal virtual returns (bool) {
52+
return amount + this.totalSupply() <= wrappingLimit;
53+
}
54+
55+
function _isNativeValid() override internal virtual returns (bool) {
56+
return isNativeAllowed;
57+
}
58+
4959
function getTokens() external view returns (address[] memory) {
5060
return tokens;
5161
}

contracts/tokens/TokenWrapper.sol

Lines changed: 104 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,73 +28,142 @@ abstract contract TokenWrapper is ERC20PresetMinterPauser, ITokenWrapper {
2828
@param tokenAddress Address of ERC20 to transfer.
2929
@param amount Amount of tokens to transfer.
3030
*/
31-
function wrap(address tokenAddress, uint256 amount) override public {
32-
require(_isValidAddress(tokenAddress), "Invalid token address");
33-
require(_isValidAmount(amount), "Invalid token amount");
34-
// transfer liquidity to the token wrapper
35-
IERC20(tokenAddress).transferFrom(_msgSender(), address(this), amount);
36-
// mint the wrapped token for the sender
37-
_mint(_msgSender(), amount);
31+
function wrap(
32+
address tokenAddress,
33+
uint256 amount
34+
) override payable public isValidWrapping(tokenAddress, amount) {
35+
if (tokenAddress == address(0)) {
36+
// mint the native value sent to the contract
37+
_mint(_msgSender(), msg.value);
38+
} else {
39+
// transfer liquidity to the token wrapper
40+
IERC20(tokenAddress).transferFrom(_msgSender(), address(this), amount);
41+
// mint the wrapped token for the sender
42+
_mint(_msgSender(), amount);
43+
}
3844
}
3945

4046
/**
4147
@notice Used to unwrap/burn the wrapper token on behalf of a sender.
4248
@param tokenAddress Address of ERC20 to unwrap into.
4349
@param amount Amount of tokens to burn.
4450
*/
45-
function unwrap(address tokenAddress, uint256 amount) override public {
46-
require(_isValidAddress(tokenAddress), "Invalid token address");
47-
require(_isValidAmount(amount), "Invalid token amount");
51+
function unwrap(
52+
address tokenAddress,
53+
uint256 amount
54+
) override public isValidUnwrapping(tokenAddress, amount) {
4855
// burn wrapped token from sender
4956
_burn(_msgSender(), amount);
50-
// transfer liquidity from the token wrapper to the sender
51-
IERC20(tokenAddress).transfer(_msgSender(), amount);
57+
// unwrap liquidity and send to the sender
58+
if (tokenAddress == address(0)) {
59+
// transfer native liquidity from the token wrapper to the sender
60+
payable(msg.sender).transfer(amount);
61+
} else {
62+
// transfer ERC20 liquidity from the token wrapper to the sender
63+
IERC20(tokenAddress).transfer(_msgSender(), amount);
64+
}
5265
}
5366

5467
/**
5568
@notice Used to wrap tokens on behalf of a sender
69+
@param sender Address of sender where assets are sent from.
5670
@param tokenAddress Address of ERC20 to transfer.
5771
@param amount Amount of tokens to transfer.
5872
*/
59-
function wrapFor(address sender, address tokenAddress, uint256 amount) override public {
60-
require(hasRole(MINTER_ROLE, msg.sender), "ERC20PresetMinterPauser: must have minter role");
61-
require(_isValidAddress(tokenAddress), "Invalid token address");
62-
require(_isValidAmount(amount), "Invalid token amount");
63-
// transfer liquidity to the token wrapper
64-
IERC20(tokenAddress).transferFrom(sender, address(this), amount);
65-
// mint the wrapped token for the sender
66-
mint(sender, amount);
73+
function wrapFor(
74+
address sender,
75+
address tokenAddress,
76+
uint256 amount
77+
) override payable public isMinter() isValidWrapping(tokenAddress, amount) {
78+
if (tokenAddress == address(0)) {
79+
mint(sender, msg.value);
80+
} else {
81+
// transfer liquidity to the token wrapper
82+
IERC20(tokenAddress).transferFrom(sender, address(this), amount);
83+
// mint the wrapped token for the sender
84+
mint(sender, amount);
85+
}
6786
}
87+
6888
/**
69-
@notice Used to wrap tokens and mint the wrapped tokens to a potentially different recipient
89+
@notice Used to wrap tokens on behalf of a sender and mint to a potentially different address
90+
@param sender Address of sender where assets are sent from.
91+
@param tokenAddress Address of ERC20 to transfer.
92+
@param amount Amount of tokens to transfer.
93+
@param recipient Recipient of the wrapped tokens.
7094
*/
71-
function wrapForAndSendTo(address sender, address tokenAddress, uint256 amount, address recipient) override public {
72-
require(hasRole(MINTER_ROLE, msg.sender), "ERC20PresetMinterPauser: must have minter role");
73-
require(_isValidAddress(tokenAddress), "Invalid token address");
74-
require(_isValidAmount(amount), "Invalid token amount");
75-
// transfer liquidity to the token wrapper
76-
IERC20(tokenAddress).transferFrom(sender, address(this), amount);
77-
// mint the wrapped token for the sender
78-
mint(recipient, amount);
95+
function wrapForAndSendTo(
96+
address sender,
97+
address tokenAddress,
98+
uint256 amount,
99+
address recipient
100+
) override payable public isMinter() isValidWrapping(tokenAddress, amount) {
101+
if (tokenAddress == address(0)) {
102+
mint(recipient, msg.value);
103+
} else {
104+
// transfer liquidity to the token wrapper
105+
IERC20(tokenAddress).transferFrom(sender, address(this), amount);
106+
// mint the wrapped token for the recipient
107+
mint(recipient, amount);
108+
}
79109
}
110+
80111
/**
81112
@notice Used to unwrap/burn the wrapper token.
82113
@param tokenAddress Address of ERC20 to unwrap into.
83114
@param amount Amount of tokens to burn.
84115
*/
85-
function unwrapFor(address sender, address tokenAddress, uint256 amount) override public {
86-
require(hasRole(MINTER_ROLE, msg.sender), "ERC20PresetMinterPauser: must have minter role");
87-
require(_isValidAddress(tokenAddress), "Invalid token address");
88-
require(_isValidAmount(amount), "Invalid token amount");
116+
function unwrapFor(
117+
address sender,
118+
address tokenAddress,
119+
uint256 amount
120+
) override public isMinter() isValidUnwrapping(tokenAddress, amount) {
89121
// burn wrapped token from sender
90122
_burn(sender, amount);
91-
// transfer liquidity from the token wrapper to the sender
92-
IERC20(tokenAddress).transfer(sender, amount);
123+
if (tokenAddress == address(0)) {
124+
payable(sender).transfer(amount);
125+
} else {
126+
// transfer liquidity from the token wrapper to the sender
127+
IERC20(tokenAddress).transfer(sender, amount);
128+
}
93129
}
94130

95131
/** @dev this function is defined in a child contract */
96132
function _isValidAddress(address tokenAddress) internal virtual returns (bool);
97133

134+
/** @dev this function is defined in a child contract */
135+
function _isNativeValid() internal virtual returns (bool);
136+
98137
/** @dev this function is defined in a child contract */
99138
function _isValidAmount(uint256 amount) internal virtual returns (bool);
139+
140+
modifier isMinter() {
141+
require(hasRole(MINTER_ROLE, msg.sender), "ERC20PresetMinterPauser: must have minter role");
142+
_;
143+
}
144+
145+
modifier isValidWrapping(address tokenAddress, uint256 amount) {
146+
if (tokenAddress == address(0)) {
147+
require(amount == 0, "Invalid amount provided for native wrapping");
148+
require(_isNativeValid(), "Native wrapping is not allowed for this token wrapper");
149+
} else {
150+
require(msg.value == 0, "Invalid value sent for wrapping");
151+
require(_isValidAddress(tokenAddress), "Invalid token address");
152+
}
153+
154+
require(_isValidAmount(amount), "Invalid token amount");
155+
_;
156+
}
157+
158+
modifier isValidUnwrapping(address tokenAddress, uint256 amount) {
159+
if (tokenAddress == address(0)) {
160+
require(address(this).balance >= amount, "Insufficient native balance");
161+
require(_isNativeValid(), "Native unwrapping is not allowed for this token wrapper");
162+
} else {
163+
require(IERC20(tokenAddress).balanceOf(address(this)) >= amount, "Insufficient ERC20 balance");
164+
require(_isValidAddress(tokenAddress), "Invalid token address");
165+
}
166+
167+
_;
168+
}
100169
}

lib/darkwebb/ERC20.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { ethers } from "ethers";
2+
import { ERC20 as ERC20Contract} from '../../typechain/ERC20';
3+
import { ERC20__factory } from "../../typechain/factories/ERC20__factory";
4+
5+
class ERC20 {
6+
contract: ERC20Contract;
7+
8+
constructor(
9+
contract: ERC20Contract
10+
) {
11+
this.contract = contract;
12+
}
13+
14+
public static async createERC20(
15+
name: string,
16+
symbol: string,
17+
deployer: ethers.Signer
18+
): Promise<ERC20> {
19+
const factory = new ERC20__factory(deployer);
20+
const contract = await factory.deploy(
21+
name,
22+
symbol,
23+
);
24+
await contract.deployed();
25+
26+
const handler = new ERC20(contract);
27+
return handler;
28+
}
29+
}
30+
31+
export default ERC20;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ethers } from "ethers";
2+
import { GovernedTokenWrapper as GovernedTokenWrapperContract} from '../../typechain/GovernedTokenWrapper';
3+
import { GovernedTokenWrapper__factory } from "../../typechain/factories/GovernedTokenWrapper__factory";
4+
5+
class GovernedTokenWrapper {
6+
contract: GovernedTokenWrapperContract;
7+
8+
constructor(
9+
contract: GovernedTokenWrapperContract
10+
) {
11+
this.contract = contract;
12+
}
13+
14+
public static async createGovernedTokenWrapper(
15+
name: string,
16+
symbol: string,
17+
governor: string,
18+
limit: string,
19+
isNativeAllowed: boolean,
20+
deployer: ethers.Signer
21+
) {
22+
const factory = new GovernedTokenWrapper__factory(deployer);
23+
const contract = await factory.deploy(
24+
name,
25+
symbol,
26+
governor,
27+
limit,
28+
isNativeAllowed
29+
);
30+
await contract.deployed();
31+
32+
const handler = new GovernedTokenWrapper(contract);
33+
return handler;
34+
}
35+
}
36+
37+
export default GovernedTokenWrapper;

scripts/bash/compile_circom.sh

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ mkdir -p artifacts/circuits/bridge
1717
# --sym artifacts/circuits/bridge/poseidon_bridge_2.sym
1818
# echo "Done!\n"
1919

20-
# echo "Compiling Webb style Poseidon bridge 3 withdrawal circuit..."
21-
# circom circuits/test/poseidon_bridge_3.circom \
22-
# --r1cs artifacts/circuits/bridge/poseidon_bridge_3.r1cs \
23-
# --wasm artifacts/circuits/bridge/poseidon_bridge_3.wasm \
24-
# --sym artifacts/circuits/bridge/poseidon_bridge_3.sym
25-
# echo "Done!\n"
20+
echo "Compiling Webb style Poseidon bridge 3 withdrawal circuit..."
21+
circom circuits/test/poseidon_bridge_3.circom \
22+
--r1cs artifacts/circuits/bridge/poseidon_bridge_3.r1cs \
23+
--wasm artifacts/circuits/bridge/poseidon_bridge_3.wasm \
24+
--sym artifacts/circuits/bridge/poseidon_bridge_3.sym
25+
echo "Done!\n"
2626

2727
# echo "Compiling Webb style Poseidon bridge 4 withdrawal circuit..."
2828
# circom circuits/test/poseidon_bridge_4.circom \
@@ -59,9 +59,9 @@ mkdir -p artifacts/circuits/bridge
5959
# --sym artifacts/circuits/poseidon_preimage_3.sym
6060
# echo "Done!"
6161

62-
echo "Compiling Set membership of length 5 circuit..."
63-
circom circuits/test/set_membership_5.circom \
64-
--r1cs artifacts/circuits/bridge/set_membership_5.r1cs \
65-
--wasm artifacts/circuits/bridge/set_membership_5.wasm \
66-
--sym artifacts/circuits/bridge/set_membership_5.sym
67-
echo "Done!\n"
62+
# echo "Compiling Set membership of length 5 circuit..."
63+
# circom circuits/test/set_membership_5.circom \
64+
# --r1cs artifacts/circuits/bridge/set_membership_5.r1cs \
65+
# --wasm artifacts/circuits/bridge/set_membership_5.wasm \
66+
# --sym artifacts/circuits/bridge/set_membership_5.sym
67+
# echo "Done!\n"

0 commit comments

Comments
 (0)