Skip to content

Commit

Permalink
Merge pull request #39 from webb-tools/drew-native-token-wrapper
Browse files Browse the repository at this point in the history
Add ability to wrap native assets into the token wrapper
  • Loading branch information
drewstone authored Oct 19, 2021
2 parents d30b84a + ac6890a commit 359a4ac
Show file tree
Hide file tree
Showing 15 changed files with 36,575 additions and 714 deletions.
23 changes: 18 additions & 5 deletions contracts/anchors/bridged/Anchor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,34 @@ contract Anchor is LinkableAnchor {
token = address(_token);
}

function wrap(address tokenAddress, uint256 amount) public {
function wrapToken(address tokenAddress, uint256 amount) public {
ITokenWrapper(token).wrapFor(msg.sender, tokenAddress, amount);
}

function unwrap(address tokenAddress, uint256 amount) public {
function unwrapIntoToken(address tokenAddress, uint256 amount) public {
ITokenWrapper(token).unwrapFor(msg.sender, tokenAddress, amount);
}

function wrapNative() payable public {
ITokenWrapper(token).wrapFor{value: msg.value}(msg.sender, address(0), 0);
}

function unwrapIntoNative(address tokenAddress, uint256 amount) public {
ITokenWrapper(token).unwrapFor(msg.sender, tokenAddress, amount);
}

function wrapAndDeposit(
address tokenAddress,
bytes32 _commitment
) public {
) payable public {
require(!commitments[_commitment], "The commitment has been submitted");
// wrap the token and send directly to this contract
ITokenWrapper(token).wrapForAndSendTo(msg.sender, tokenAddress, denomination, address(this));
// wrap into the token and send directly to this contract
ITokenWrapper(token).wrapForAndSendTo{value: msg.value}(
msg.sender,
tokenAddress,
denomination,
address(this)
);
// insert a new commitment to the tree
uint32 insertedIndex = _insert(_commitment);
commitments[_commitment] = true;
Expand Down
6 changes: 3 additions & 3 deletions contracts/interfaces/ITokenWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ pragma solidity ^0.8.0;
@author Webb Technologies.
*/
interface ITokenWrapper {
function wrap(address tokenAddress, uint256 amount) external;
function wrap(address tokenAddress, uint256 amount) payable external;
function unwrap(address tokenAddress, uint256 amount) external;
function wrapFor(address sender, address tokenAddress, uint256 amount) external;
function wrapForAndSendTo(address sender, address tokenAddress, uint256 amount, address mintRecipient) external;
function wrapFor(address sender, address tokenAddress, uint256 amount) payable external;
function wrapForAndSendTo(address sender, address tokenAddress, uint256 amount, address mintRecipient) payable external;
function unwrapFor(address sender, address tokenAddress, uint256 amount) external;
}
2 changes: 1 addition & 1 deletion contracts/mocks/GTokenWrapperMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ contract GTokenWrapperMock is GovernedTokenWrapper {
* @notice Construct a new Comp token
*/
constructor(string memory name, string memory symbol, address governor, uint256 limit)
GovernedTokenWrapper(name, symbol, governor, limit) {}
GovernedTokenWrapper(name, symbol, governor, limit, true) {}
}
24 changes: 17 additions & 7 deletions contracts/tokens/GovernedTokenWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,21 @@ contract GovernedTokenWrapper is TokenWrapper {
address[] public tokens;
mapping (address => bool) valid;

bool isNativeAllowed;
uint256 public wrappingLimit;

constructor(string memory name, string memory symbol, address _governor, uint256 _limit) TokenWrapper(name, symbol) {
constructor(string memory name, string memory symbol, address _governor, uint256 _limit, bool _isNativeAllowed) TokenWrapper(name, symbol) {
governor = _governor;
wrappingLimit = _limit;
isNativeAllowed = _isNativeAllowed;
}

function setGovernor(address _governor) public onlyGovernor {
governor = _governor;
}

function _isValidAddress(address tokenAddress) override internal virtual returns (bool) {
return valid[tokenAddress];
}

function _isValidAmount(uint256 amount) override internal virtual returns (bool) {
return amount + this.totalSupply() <= wrappingLimit;
function setNativeAllowed(bool _isNativeAllowed) public onlyGovernor {
isNativeAllowed = _isNativeAllowed;
}

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

function _isValidAddress(address tokenAddress) override internal virtual returns (bool) {
return valid[tokenAddress];
}

function _isValidAmount(uint256 amount) override internal virtual returns (bool) {
return amount + this.totalSupply() <= wrappingLimit;
}

function _isNativeValid() override internal virtual returns (bool) {
return isNativeAllowed;
}

function getTokens() external view returns (address[] memory) {
return tokens;
}
Expand Down
139 changes: 104 additions & 35 deletions contracts/tokens/TokenWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,73 +28,142 @@ abstract contract TokenWrapper is ERC20PresetMinterPauser, ITokenWrapper {
@param tokenAddress Address of ERC20 to transfer.
@param amount Amount of tokens to transfer.
*/
function wrap(address tokenAddress, uint256 amount) override public {
require(_isValidAddress(tokenAddress), "Invalid token address");
require(_isValidAmount(amount), "Invalid token amount");
// transfer liquidity to the token wrapper
IERC20(tokenAddress).transferFrom(_msgSender(), address(this), amount);
// mint the wrapped token for the sender
_mint(_msgSender(), amount);
function wrap(
address tokenAddress,
uint256 amount
) override payable public isValidWrapping(tokenAddress, amount) {
if (tokenAddress == address(0)) {
// mint the native value sent to the contract
_mint(_msgSender(), msg.value);
} else {
// transfer liquidity to the token wrapper
IERC20(tokenAddress).transferFrom(_msgSender(), address(this), amount);
// mint the wrapped token for the sender
_mint(_msgSender(), amount);
}
}

/**
@notice Used to unwrap/burn the wrapper token on behalf of a sender.
@param tokenAddress Address of ERC20 to unwrap into.
@param amount Amount of tokens to burn.
*/
function unwrap(address tokenAddress, uint256 amount) override public {
require(_isValidAddress(tokenAddress), "Invalid token address");
require(_isValidAmount(amount), "Invalid token amount");
function unwrap(
address tokenAddress,
uint256 amount
) override public isValidUnwrapping(tokenAddress, amount) {
// burn wrapped token from sender
_burn(_msgSender(), amount);
// transfer liquidity from the token wrapper to the sender
IERC20(tokenAddress).transfer(_msgSender(), amount);
// unwrap liquidity and send to the sender
if (tokenAddress == address(0)) {
// transfer native liquidity from the token wrapper to the sender
payable(msg.sender).transfer(amount);
} else {
// transfer ERC20 liquidity from the token wrapper to the sender
IERC20(tokenAddress).transfer(_msgSender(), amount);
}
}

/**
@notice Used to wrap tokens on behalf of a sender
@param sender Address of sender where assets are sent from.
@param tokenAddress Address of ERC20 to transfer.
@param amount Amount of tokens to transfer.
*/
function wrapFor(address sender, address tokenAddress, uint256 amount) override public {
require(hasRole(MINTER_ROLE, msg.sender), "ERC20PresetMinterPauser: must have minter role");
require(_isValidAddress(tokenAddress), "Invalid token address");
require(_isValidAmount(amount), "Invalid token amount");
// transfer liquidity to the token wrapper
IERC20(tokenAddress).transferFrom(sender, address(this), amount);
// mint the wrapped token for the sender
mint(sender, amount);
function wrapFor(
address sender,
address tokenAddress,
uint256 amount
) override payable public isMinter() isValidWrapping(tokenAddress, amount) {
if (tokenAddress == address(0)) {
mint(sender, msg.value);
} else {
// transfer liquidity to the token wrapper
IERC20(tokenAddress).transferFrom(sender, address(this), amount);
// mint the wrapped token for the sender
mint(sender, amount);
}
}

/**
@notice Used to wrap tokens and mint the wrapped tokens to a potentially different recipient
@notice Used to wrap tokens on behalf of a sender and mint to a potentially different address
@param sender Address of sender where assets are sent from.
@param tokenAddress Address of ERC20 to transfer.
@param amount Amount of tokens to transfer.
@param recipient Recipient of the wrapped tokens.
*/
function wrapForAndSendTo(address sender, address tokenAddress, uint256 amount, address recipient) override public {
require(hasRole(MINTER_ROLE, msg.sender), "ERC20PresetMinterPauser: must have minter role");
require(_isValidAddress(tokenAddress), "Invalid token address");
require(_isValidAmount(amount), "Invalid token amount");
// transfer liquidity to the token wrapper
IERC20(tokenAddress).transferFrom(sender, address(this), amount);
// mint the wrapped token for the sender
mint(recipient, amount);
function wrapForAndSendTo(
address sender,
address tokenAddress,
uint256 amount,
address recipient
) override payable public isMinter() isValidWrapping(tokenAddress, amount) {
if (tokenAddress == address(0)) {
mint(recipient, msg.value);
} else {
// transfer liquidity to the token wrapper
IERC20(tokenAddress).transferFrom(sender, address(this), amount);
// mint the wrapped token for the recipient
mint(recipient, amount);
}
}

/**
@notice Used to unwrap/burn the wrapper token.
@param tokenAddress Address of ERC20 to unwrap into.
@param amount Amount of tokens to burn.
*/
function unwrapFor(address sender, address tokenAddress, uint256 amount) override public {
require(hasRole(MINTER_ROLE, msg.sender), "ERC20PresetMinterPauser: must have minter role");
require(_isValidAddress(tokenAddress), "Invalid token address");
require(_isValidAmount(amount), "Invalid token amount");
function unwrapFor(
address sender,
address tokenAddress,
uint256 amount
) override public isMinter() isValidUnwrapping(tokenAddress, amount) {
// burn wrapped token from sender
_burn(sender, amount);
// transfer liquidity from the token wrapper to the sender
IERC20(tokenAddress).transfer(sender, amount);
if (tokenAddress == address(0)) {
payable(sender).transfer(amount);
} else {
// transfer liquidity from the token wrapper to the sender
IERC20(tokenAddress).transfer(sender, amount);
}
}

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

/** @dev this function is defined in a child contract */
function _isNativeValid() internal virtual returns (bool);

/** @dev this function is defined in a child contract */
function _isValidAmount(uint256 amount) internal virtual returns (bool);

modifier isMinter() {
require(hasRole(MINTER_ROLE, msg.sender), "ERC20PresetMinterPauser: must have minter role");
_;
}

modifier isValidWrapping(address tokenAddress, uint256 amount) {
if (tokenAddress == address(0)) {
require(amount == 0, "Invalid amount provided for native wrapping");
require(_isNativeValid(), "Native wrapping is not allowed for this token wrapper");
} else {
require(msg.value == 0, "Invalid value sent for wrapping");
require(_isValidAddress(tokenAddress), "Invalid token address");
}

require(_isValidAmount(amount), "Invalid token amount");
_;
}

modifier isValidUnwrapping(address tokenAddress, uint256 amount) {
if (tokenAddress == address(0)) {
require(address(this).balance >= amount, "Insufficient native balance");
require(_isNativeValid(), "Native unwrapping is not allowed for this token wrapper");
} else {
require(IERC20(tokenAddress).balanceOf(address(this)) >= amount, "Insufficient ERC20 balance");
require(_isValidAddress(tokenAddress), "Invalid token address");
}

_;
}
}
31 changes: 31 additions & 0 deletions lib/darkwebb/ERC20.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ethers } from "ethers";
import { ERC20 as ERC20Contract} from '../../typechain/ERC20';
import { ERC20__factory } from "../../typechain/factories/ERC20__factory";

class ERC20 {
contract: ERC20Contract;

constructor(
contract: ERC20Contract
) {
this.contract = contract;
}

public static async createERC20(
name: string,
symbol: string,
deployer: ethers.Signer
): Promise<ERC20> {
const factory = new ERC20__factory(deployer);
const contract = await factory.deploy(
name,
symbol,
);
await contract.deployed();

const handler = new ERC20(contract);
return handler;
}
}

export default ERC20;
37 changes: 37 additions & 0 deletions lib/darkwebb/GovernedTokenWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ethers } from "ethers";
import { GovernedTokenWrapper as GovernedTokenWrapperContract} from '../../typechain/GovernedTokenWrapper';
import { GovernedTokenWrapper__factory } from "../../typechain/factories/GovernedTokenWrapper__factory";

class GovernedTokenWrapper {
contract: GovernedTokenWrapperContract;

constructor(
contract: GovernedTokenWrapperContract
) {
this.contract = contract;
}

public static async createGovernedTokenWrapper(
name: string,
symbol: string,
governor: string,
limit: string,
isNativeAllowed: boolean,
deployer: ethers.Signer
) {
const factory = new GovernedTokenWrapper__factory(deployer);
const contract = await factory.deploy(
name,
symbol,
governor,
limit,
isNativeAllowed
);
await contract.deployed();

const handler = new GovernedTokenWrapper(contract);
return handler;
}
}

export default GovernedTokenWrapper;
24 changes: 12 additions & 12 deletions scripts/bash/compile_circom.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ mkdir -p artifacts/circuits/bridge
# --sym artifacts/circuits/bridge/poseidon_bridge_2.sym
# echo "Done!\n"

# echo "Compiling Webb style Poseidon bridge 3 withdrawal circuit..."
# circom circuits/test/poseidon_bridge_3.circom \
# --r1cs artifacts/circuits/bridge/poseidon_bridge_3.r1cs \
# --wasm artifacts/circuits/bridge/poseidon_bridge_3.wasm \
# --sym artifacts/circuits/bridge/poseidon_bridge_3.sym
# echo "Done!\n"
echo "Compiling Webb style Poseidon bridge 3 withdrawal circuit..."
circom circuits/test/poseidon_bridge_3.circom \
--r1cs artifacts/circuits/bridge/poseidon_bridge_3.r1cs \
--wasm artifacts/circuits/bridge/poseidon_bridge_3.wasm \
--sym artifacts/circuits/bridge/poseidon_bridge_3.sym
echo "Done!\n"

# echo "Compiling Webb style Poseidon bridge 4 withdrawal circuit..."
# circom circuits/test/poseidon_bridge_4.circom \
Expand Down Expand Up @@ -59,9 +59,9 @@ mkdir -p artifacts/circuits/bridge
# --sym artifacts/circuits/poseidon_preimage_3.sym
# echo "Done!"

echo "Compiling Set membership of length 5 circuit..."
circom circuits/test/set_membership_5.circom \
--r1cs artifacts/circuits/bridge/set_membership_5.r1cs \
--wasm artifacts/circuits/bridge/set_membership_5.wasm \
--sym artifacts/circuits/bridge/set_membership_5.sym
echo "Done!\n"
# echo "Compiling Set membership of length 5 circuit..."
# circom circuits/test/set_membership_5.circom \
# --r1cs artifacts/circuits/bridge/set_membership_5.r1cs \
# --wasm artifacts/circuits/bridge/set_membership_5.wasm \
# --sym artifacts/circuits/bridge/set_membership_5.sym
# echo "Done!\n"
Loading

0 comments on commit 359a4ac

Please sign in to comment.