tokenbridge: decimal shifting & max outstanding
Change-Id: Ia9f27f317fe08c1d8dbb9eaa60e53633acfdd381
This commit is contained in:
parent
14e892300c
commit
51e00dc1bf
|
@ -60,32 +60,77 @@ contract Bridge is BridgeGovernance {
|
||||||
}(nonce, encoded, 15);
|
}(nonce, encoded, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapAndTransferETH(uint16 recipientChain, bytes32 recipient, uint256 fee, uint32 nonce) public payable returns (uint64 sequence) {
|
function wrapAndTransferETH(uint16 recipientChain, bytes32 recipient, uint256 arbiterFee, uint32 nonce) public payable returns (uint64 sequence) {
|
||||||
uint wormholeFee = wormhole().messageFee();
|
uint wormholeFee = wormhole().messageFee();
|
||||||
|
|
||||||
require(wormholeFee < msg.value, "value is smaller than wormhole fee");
|
require(wormholeFee < msg.value, "value is smaller than wormhole fee");
|
||||||
|
|
||||||
|
uint amount = msg.value - wormholeFee;
|
||||||
|
|
||||||
|
require(arbiterFee <= amount, "fee is bigger than amount minus wormhole fee");
|
||||||
|
|
||||||
|
uint normalizedAmount = amount / (10**10);
|
||||||
|
uint normalizedArbiterFee = arbiterFee / (10**10);
|
||||||
|
|
||||||
|
// refund dust
|
||||||
|
uint dust = amount - (normalizedAmount * (10**10));
|
||||||
|
if (dust > 0) {
|
||||||
|
payable(msg.sender).transfer(dust);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deposit into WETH
|
||||||
WETH().deposit{
|
WETH().deposit{
|
||||||
value : msg.value - wormholeFee
|
value : amount - dust
|
||||||
}();
|
}();
|
||||||
|
|
||||||
sequence = logTransfer(chainId(), bytes32(uint256(uint160(address(WETH())))), msg.value, recipientChain, recipient, fee, wormholeFee, nonce);
|
// track and check outstanding token amounts
|
||||||
|
bridgeOut(address(WETH()), normalizedAmount);
|
||||||
|
|
||||||
|
sequence = logTransfer(chainId(), bytes32(uint256(uint160(address(WETH())))), normalizedAmount, recipientChain, recipient, normalizedArbiterFee, wormholeFee, nonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiate a Transfer
|
// Initiate a Transfer
|
||||||
function transferTokens(uint16 tokenChain, bytes32 tokenAddress, uint256 amount, uint16 recipientChain, bytes32 recipient, uint256 fee, uint32 nonce) public payable returns (uint64 sequence) {
|
function transferTokens(address token, uint256 amount, uint16 recipientChain, bytes32 recipient, uint256 arbiterFee, uint32 nonce) public payable returns (uint64 sequence) {
|
||||||
if(tokenChain == chainId()){
|
// determine token parameters
|
||||||
SafeERC20.safeTransferFrom(IERC20(address(uint160(uint256(tokenAddress)))), msg.sender, address(this), amount);
|
uint16 tokenChain;
|
||||||
} else {
|
bytes32 tokenAddress;
|
||||||
address wrapped = wrappedAsset(tokenChain, tokenAddress);
|
if(isWrappedAsset(token)){
|
||||||
require(wrapped != address(0), "no wrapper for this token created yet");
|
tokenChain = TokenImplementation(token).chainId();
|
||||||
|
tokenAddress = TokenImplementation(token).nativeContract();
|
||||||
SafeERC20.safeTransferFrom(IERC20(wrapped), msg.sender, address(this), amount);
|
}else{
|
||||||
|
tokenChain = chainId();
|
||||||
TokenImplementation(wrapped).burn(address(this), amount);
|
tokenAddress = bytes32(uint256(uint160(token)));
|
||||||
}
|
}
|
||||||
|
|
||||||
sequence = logTransfer(tokenChain, tokenAddress, amount, recipientChain, recipient, fee, msg.value, nonce);
|
// query tokens decimals
|
||||||
|
(,bytes memory queriedDecimals) = token.staticcall(abi.encodeWithSignature("decimals()"));
|
||||||
|
uint8 decimals = abi.decode(queriedDecimals, (uint8));
|
||||||
|
|
||||||
|
// adjust decimals
|
||||||
|
uint256 normalizedAmount = amount;
|
||||||
|
uint256 normalizedArbiterFee = arbiterFee;
|
||||||
|
if(decimals > 8) {
|
||||||
|
uint multiplier = 10**(decimals - 8);
|
||||||
|
|
||||||
|
normalizedAmount /= multiplier;
|
||||||
|
normalizedArbiterFee /= multiplier;
|
||||||
|
|
||||||
|
// don't deposit dust that can not be bridged due to the decimal shift
|
||||||
|
amount = normalizedAmount * multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tokenChain == chainId()){
|
||||||
|
SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(this), amount);
|
||||||
|
|
||||||
|
// track and check outstanding token amounts
|
||||||
|
bridgeOut(token, normalizedAmount);
|
||||||
|
} else {
|
||||||
|
SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(this), amount);
|
||||||
|
|
||||||
|
TokenImplementation(token).burn(address(this), amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence = logTransfer(tokenChain, tokenAddress, normalizedAmount, recipientChain, recipient, normalizedArbiterFee, msg.value, nonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
function logTransfer(uint16 tokenChain, bytes32 tokenAddress, uint256 amount, uint16 recipientChain, bytes32 recipient, uint256 fee, uint256 callValue, uint32 nonce) internal returns (uint64 sequence) {
|
function logTransfer(uint16 tokenChain, bytes32 tokenAddress, uint256 amount, uint16 recipientChain, bytes32 recipient, uint256 fee, uint256 callValue, uint32 nonce) internal returns (uint64 sequence) {
|
||||||
|
@ -180,39 +225,72 @@ contract Bridge is BridgeGovernance {
|
||||||
IERC20 transferToken;
|
IERC20 transferToken;
|
||||||
if(transfer.tokenChain == chainId()){
|
if(transfer.tokenChain == chainId()){
|
||||||
transferToken = IERC20(address(uint160(uint256(transfer.tokenAddress))));
|
transferToken = IERC20(address(uint160(uint256(transfer.tokenAddress))));
|
||||||
|
|
||||||
|
// track outstanding token amounts
|
||||||
|
bridgedIn(address(transferToken), transfer.amount);
|
||||||
} else {
|
} else {
|
||||||
address wrapped = wrappedAsset(transfer.tokenChain, transfer.tokenAddress);
|
address wrapped = wrappedAsset(transfer.tokenChain, transfer.tokenAddress);
|
||||||
require(wrapped != address(0), "no wrapper for this token created yet");
|
require(wrapped != address(0), "no wrapper for this token created yet");
|
||||||
|
|
||||||
TokenImplementation(wrapped).mint(address(this), transfer.amount);
|
|
||||||
|
|
||||||
transferToken = IERC20(wrapped);
|
transferToken = IERC20(wrapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(transfer.fee > 0) {
|
require(unwrapWETH == false || address(transferToken) == address(WETH()), "invalid token, can only unwrap WETH");
|
||||||
require(transfer.fee <= transfer.amount, "fee higher than transferred amount");
|
|
||||||
|
// query decimals
|
||||||
|
(,bytes memory queriedDecimals) = address(transferToken).staticcall(abi.encodeWithSignature("decimals()"));
|
||||||
|
uint8 decimals = abi.decode(queriedDecimals, (uint8));
|
||||||
|
|
||||||
|
// adjust decimals
|
||||||
|
uint256 nativeAmount = transfer.amount;
|
||||||
|
uint256 nativeFee = transfer.fee;
|
||||||
|
if(decimals > 8) {
|
||||||
|
uint multiplier = 10**(decimals - 8);
|
||||||
|
nativeAmount *= multiplier;
|
||||||
|
nativeFee *= multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mint wrapped asset
|
||||||
|
if(transfer.tokenChain != chainId()) {
|
||||||
|
TokenImplementation(address(transferToken)).mint(address(this), nativeAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// transfer fee to arbiter
|
||||||
|
if(nativeFee > 0) {
|
||||||
|
require(nativeFee <= nativeAmount, "fee higher than transferred amount");
|
||||||
|
|
||||||
if (unwrapWETH) {
|
if (unwrapWETH) {
|
||||||
require(address(transferToken) == address(WETH()), "invalid token, can only unwrap ETH");
|
WETH().withdraw(nativeFee);
|
||||||
WETH().withdraw(transfer.fee);
|
|
||||||
payable(msg.sender).transfer(transfer.fee);
|
payable(msg.sender).transfer(nativeFee);
|
||||||
} else {
|
} else {
|
||||||
SafeERC20.safeTransfer(transferToken, msg.sender, transfer.fee);
|
SafeERC20.safeTransfer(transferToken, msg.sender, nativeFee);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint transferAmount = transfer.amount - transfer.fee;
|
// transfer bridged amount to recipient
|
||||||
address payable transferRecipient = payable(address(uint160(uint256(transfer.to))));
|
uint transferAmount = nativeAmount - nativeFee;
|
||||||
|
address transferRecipient = address(uint160(uint256(transfer.to)));
|
||||||
|
|
||||||
if (unwrapWETH) {
|
if (unwrapWETH) {
|
||||||
require(address(transferToken) == address(WETH()), "invalid token, can only unwrap ETH");
|
|
||||||
WETH().withdraw(transferAmount);
|
WETH().withdraw(transferAmount);
|
||||||
transferRecipient.transfer(transferAmount);
|
|
||||||
|
payable(transferRecipient).transfer(transferAmount);
|
||||||
} else {
|
} else {
|
||||||
SafeERC20.safeTransfer(transferToken, transferRecipient, transferAmount);
|
SafeERC20.safeTransfer(transferToken, transferRecipient, transferAmount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bridgeOut(address token, uint normalizedAmount) internal {
|
||||||
|
uint outstanding = outstandingBridged(token);
|
||||||
|
require(outstanding + normalizedAmount <= type(uint64).max, "transfer exceeds max outstanding bridged token amount");
|
||||||
|
setOutstandingBridged(token, outstanding + normalizedAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bridgedIn(address token, uint normalizedAmount) internal {
|
||||||
|
setOutstandingBridged(token, outstandingBridged(token) - normalizedAmount);
|
||||||
|
}
|
||||||
|
|
||||||
function verifyBridgeVM(IWormhole.VM memory vm) internal view returns (bool){
|
function verifyBridgeVM(IWormhole.VM memory vm) internal view returns (bool){
|
||||||
if (bridgeContracts(vm.emitterChainId) == vm.emitterAddress) {
|
if (bridgeContracts(vm.emitterChainId) == vm.emitterAddress) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -53,6 +53,14 @@ contract BridgeGetters is BridgeState {
|
||||||
function WETH() public view returns (IWETH){
|
function WETH() public view returns (IWETH){
|
||||||
return IWETH(_state.provider.WETH);
|
return IWETH(_state.provider.WETH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function outstandingBridged(address token) public view returns (uint256){
|
||||||
|
return _state.outstandingBridged[token];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWrappedAsset(address token) public view returns (bool){
|
||||||
|
return _state.isWrappedAsset[token];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IWETH is IERC20 {
|
interface IWETH is IERC20 {
|
||||||
|
|
|
@ -48,5 +48,10 @@ contract BridgeSetters is BridgeState {
|
||||||
|
|
||||||
function setWrappedAsset(uint16 tokenChainId, bytes32 tokenAddress, address wrapper) internal {
|
function setWrappedAsset(uint16 tokenChainId, bytes32 tokenAddress, address wrapper) internal {
|
||||||
_state.wrappedAssets[tokenChainId][tokenAddress] = wrapper;
|
_state.wrappedAssets[tokenChainId][tokenAddress] = wrapper;
|
||||||
|
_state.isWrappedAsset[wrapper] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOutstandingBridged(address token, uint256 outstanding) internal {
|
||||||
|
_state.outstandingBridged[token] = outstanding;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,6 +13,11 @@ contract BridgeStorage {
|
||||||
address WETH;
|
address WETH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Asset {
|
||||||
|
uint16 chainId;
|
||||||
|
bytes32 assetAddress;
|
||||||
|
}
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
address payable wormhole;
|
address payable wormhole;
|
||||||
address tokenImplementation;
|
address tokenImplementation;
|
||||||
|
@ -31,6 +36,12 @@ contract BridgeStorage {
|
||||||
// Mapping of wrapped assets (chainID => nativeAddress => wrappedAddress)
|
// Mapping of wrapped assets (chainID => nativeAddress => wrappedAddress)
|
||||||
mapping(uint16 => mapping(bytes32 => address)) wrappedAssets;
|
mapping(uint16 => mapping(bytes32 => address)) wrappedAssets;
|
||||||
|
|
||||||
|
// Mapping to safely identify wrapped assets
|
||||||
|
mapping(address => bool) isWrappedAsset;
|
||||||
|
|
||||||
|
// Mapping of native assets to amount outstanding on other chains
|
||||||
|
mapping(address => uint256) outstandingBridged;
|
||||||
|
|
||||||
// Mapping of bridge contracts on other chains
|
// Mapping of bridge contracts on other chains
|
||||||
mapping(uint16 => bytes32) bridgeImplementations;
|
mapping(uint16 => bytes32) bridgeImplementations;
|
||||||
}
|
}
|
||||||
|
|
|
@ -303,6 +303,8 @@ contract("Bridge", function () {
|
||||||
|
|
||||||
const wrappedAddress = await initialized.methods.wrappedAsset("0x0001", "0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e").call();
|
const wrappedAddress = await initialized.methods.wrappedAsset("0x0001", "0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e").call();
|
||||||
|
|
||||||
|
assert.ok(await initialized.methods.isWrappedAsset(wrappedAddress).call())
|
||||||
|
|
||||||
const initializedWrappedAsset = new web3.eth.Contract(TokenImplementation.abi, wrappedAddress);
|
const initializedWrappedAsset = new web3.eth.Contract(TokenImplementation.abi, wrappedAddress);
|
||||||
|
|
||||||
const symbol = await initializedWrappedAsset.methods.symbol().call();
|
const symbol = await initializedWrappedAsset.methods.symbol().call();
|
||||||
|
@ -324,6 +326,7 @@ contract("Bridge", function () {
|
||||||
it("should deposit and log transfers correctly", async function() {
|
it("should deposit and log transfers correctly", async function() {
|
||||||
const accounts = await web3.eth.getAccounts();
|
const accounts = await web3.eth.getAccounts();
|
||||||
const amount = "1000000000000000000";
|
const amount = "1000000000000000000";
|
||||||
|
const fee = "100000000000000000";
|
||||||
|
|
||||||
// mint and approve tokens
|
// mint and approve tokens
|
||||||
const token = new web3.eth.Contract(TokenImplementation.abi, TokenImplementation.address);
|
const token = new web3.eth.Contract(TokenImplementation.abi, TokenImplementation.address);
|
||||||
|
@ -348,12 +351,11 @@ contract("Bridge", function () {
|
||||||
assert.equal(bridgeBalanceBefore.toString(10), "0");
|
assert.equal(bridgeBalanceBefore.toString(10), "0");
|
||||||
|
|
||||||
await initialized.methods.transferTokens(
|
await initialized.methods.transferTokens(
|
||||||
testChainId,
|
TokenImplementation.address,
|
||||||
web3.eth.abi.encodeParameter("address", TokenImplementation.address),
|
|
||||||
amount,
|
amount,
|
||||||
"10",
|
"10",
|
||||||
"0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
|
"0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
|
||||||
"123",
|
fee,
|
||||||
"234"
|
"234"
|
||||||
).send({
|
).send({
|
||||||
value : 0,
|
value : 0,
|
||||||
|
@ -381,7 +383,7 @@ contract("Bridge", function () {
|
||||||
assert.equal(log.payload.substr(2, 2), "01");
|
assert.equal(log.payload.substr(2, 2), "01");
|
||||||
|
|
||||||
// amount
|
// amount
|
||||||
assert.equal(log.payload.substr(4, 64), web3.eth.abi.encodeParameter("uint256", amount).substring(2));
|
assert.equal(log.payload.substr(4, 64), web3.eth.abi.encodeParameter("uint256", new BigNumber(amount).div(1e10).toString()).substring(2));
|
||||||
|
|
||||||
// token
|
// token
|
||||||
assert.equal(log.payload.substr(68, 64), web3.eth.abi.encodeParameter("address", TokenImplementation.address).substring(2));
|
assert.equal(log.payload.substr(68, 64), web3.eth.abi.encodeParameter("address", TokenImplementation.address).substring(2));
|
||||||
|
@ -396,7 +398,7 @@ contract("Bridge", function () {
|
||||||
assert.equal(log.payload.substr(200, 4), web3.eth.abi.encodeParameter("uint16", 10).substring(2 + 64 - 4))
|
assert.equal(log.payload.substr(200, 4), web3.eth.abi.encodeParameter("uint16", 10).substring(2 + 64 - 4))
|
||||||
|
|
||||||
// fee
|
// fee
|
||||||
assert.equal(log.payload.substr(204, 64), web3.eth.abi.encodeParameter("uint256", 123).substring(2))
|
assert.equal(log.payload.substr(204, 64), web3.eth.abi.encodeParameter("uint256", new BigNumber(fee).div(1e10).toString()).substring(2))
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should transfer out locked assets for a valid transfer vm", async function() {
|
it("should transfer out locked assets for a valid transfer vm", async function() {
|
||||||
|
@ -416,7 +418,7 @@ contract("Bridge", function () {
|
||||||
const data = "0x" +
|
const data = "0x" +
|
||||||
"01" +
|
"01" +
|
||||||
// amount
|
// amount
|
||||||
"0000000000000000000000000000000000000000000000000de0b6b3a7640000" +
|
web3.eth.abi.encodeParameter("uint256", new BigNumber(amount).div(1e10).toString()).substring(2) +
|
||||||
// tokenaddress
|
// tokenaddress
|
||||||
web3.eth.abi.encodeParameter("address", TokenImplementation.address).substr(2) +
|
web3.eth.abi.encodeParameter("address", TokenImplementation.address).substr(2) +
|
||||||
// tokenchain
|
// tokenchain
|
||||||
|
@ -428,8 +430,6 @@ contract("Bridge", function () {
|
||||||
// fee
|
// fee
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000";
|
"0000000000000000000000000000000000000000000000000000000000000000";
|
||||||
|
|
||||||
// console.log(data)
|
|
||||||
|
|
||||||
const vm = await signAndEncodeVM(
|
const vm = await signAndEncodeVM(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -473,7 +473,7 @@ contract("Bridge", function () {
|
||||||
const data = "0x" +
|
const data = "0x" +
|
||||||
"01" +
|
"01" +
|
||||||
// amount
|
// amount
|
||||||
"0000000000000000000000000000000000000000000000000de0b6b3a7640000" +
|
web3.eth.abi.encodeParameter("uint256", new BigNumber(amount).div(1e10).toString()).substring(2) +
|
||||||
// tokenaddress
|
// tokenaddress
|
||||||
testBridgedAssetAddress +
|
testBridgedAssetAddress +
|
||||||
// tokenchain
|
// tokenchain
|
||||||
|
@ -534,8 +534,7 @@ contract("Bridge", function () {
|
||||||
assert.equal(accountBalanceBefore.toString(10), amount);
|
assert.equal(accountBalanceBefore.toString(10), amount);
|
||||||
|
|
||||||
await initialized.methods.transferTokens(
|
await initialized.methods.transferTokens(
|
||||||
"0x"+testBridgedAssetChain,
|
wrappedAddress,
|
||||||
"0x"+testBridgedAssetAddress,
|
|
||||||
amount,
|
amount,
|
||||||
"11",
|
"11",
|
||||||
"0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
|
"0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
|
||||||
|
@ -560,6 +559,7 @@ contract("Bridge", function () {
|
||||||
it("should handle ETH deposits correctly", async function() {
|
it("should handle ETH deposits correctly", async function() {
|
||||||
const accounts = await web3.eth.getAccounts();
|
const accounts = await web3.eth.getAccounts();
|
||||||
const amount = "100000000000000000";
|
const amount = "100000000000000000";
|
||||||
|
const fee = "10000000000000000";
|
||||||
|
|
||||||
// mint and approve tokens
|
// mint and approve tokens
|
||||||
WETH = (await MockWETH9.new()).address;
|
WETH = (await MockWETH9.new()).address;
|
||||||
|
@ -584,7 +584,7 @@ contract("Bridge", function () {
|
||||||
await initialized.methods.wrapAndTransferETH(
|
await initialized.methods.wrapAndTransferETH(
|
||||||
"10",
|
"10",
|
||||||
"0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
|
"0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
|
||||||
"123",
|
fee,
|
||||||
"234"
|
"234"
|
||||||
).send({
|
).send({
|
||||||
value : amount,
|
value : amount,
|
||||||
|
@ -612,7 +612,7 @@ contract("Bridge", function () {
|
||||||
assert.equal(log.payload.substr(2, 2), "01");
|
assert.equal(log.payload.substr(2, 2), "01");
|
||||||
|
|
||||||
// amount
|
// amount
|
||||||
assert.equal(log.payload.substr(4, 64), web3.eth.abi.encodeParameter("uint256", amount).substring(2));
|
assert.equal(log.payload.substr(4, 64), web3.eth.abi.encodeParameter("uint256", new BigNumber(amount).div(1e10).toString()).substring(2));
|
||||||
|
|
||||||
// token
|
// token
|
||||||
assert.equal(log.payload.substr(68, 64), web3.eth.abi.encodeParameter("address", WETH).substring(2));
|
assert.equal(log.payload.substr(68, 64), web3.eth.abi.encodeParameter("address", WETH).substring(2));
|
||||||
|
@ -627,7 +627,7 @@ contract("Bridge", function () {
|
||||||
assert.equal(log.payload.substr(200, 4), web3.eth.abi.encodeParameter("uint16", 10).substring(2 + 64 - 4))
|
assert.equal(log.payload.substr(200, 4), web3.eth.abi.encodeParameter("uint16", 10).substring(2 + 64 - 4))
|
||||||
|
|
||||||
// fee
|
// fee
|
||||||
assert.equal(log.payload.substr(204, 64), web3.eth.abi.encodeParameter("uint256", 123).substring(2))
|
assert.equal(log.payload.substr(204, 64), web3.eth.abi.encodeParameter("uint256", new BigNumber(fee).div(1e10).toString()).substring(2))
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should handle ETH withdrawals and fees correctly", async function() {
|
it("should handle ETH withdrawals and fees correctly", async function() {
|
||||||
|
@ -649,7 +649,7 @@ contract("Bridge", function () {
|
||||||
const data = "0x" +
|
const data = "0x" +
|
||||||
"01" +
|
"01" +
|
||||||
// amount
|
// amount
|
||||||
web3.eth.abi.encodeParameter("uint256", amount).substr(2) +
|
web3.eth.abi.encodeParameter("uint256", new BigNumber(amount).div(1e10).toString()).substring(2) +
|
||||||
// tokenaddress
|
// tokenaddress
|
||||||
web3.eth.abi.encodeParameter("address", WETH).substr(2) +
|
web3.eth.abi.encodeParameter("address", WETH).substr(2) +
|
||||||
// tokenchain
|
// tokenchain
|
||||||
|
@ -659,7 +659,7 @@ contract("Bridge", function () {
|
||||||
// receiving chain
|
// receiving chain
|
||||||
web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)) +
|
web3.eth.abi.encodeParameter("uint16", testChainId).substring(2 + (64 - 4)) +
|
||||||
// fee
|
// fee
|
||||||
web3.eth.abi.encodeParameter("uint256", fee).substr(2);
|
web3.eth.abi.encodeParameter("uint256", new BigNumber(fee).div(1e10).toString()).substring(2);
|
||||||
|
|
||||||
const vm = await signAndEncodeVM(
|
const vm = await signAndEncodeVM(
|
||||||
0,
|
0,
|
||||||
|
@ -689,6 +689,62 @@ contract("Bridge", function () {
|
||||||
assert.equal((new BigNumber(accountBalanceAfter)).minus(accountBalanceBefore).toString(10), (new BigNumber(amount)).minus(fee).toString(10))
|
assert.equal((new BigNumber(accountBalanceAfter)).minus(accountBalanceBefore).toString(10), (new BigNumber(amount)).minus(fee).toString(10))
|
||||||
assert.ok((new BigNumber(feeRecipientBalanceAfter)).gt(feeRecipientBalanceBefore))
|
assert.ok((new BigNumber(feeRecipientBalanceAfter)).gt(feeRecipientBalanceBefore))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should revert on transfer out of a total of > max(uint64) tokens", async function() {
|
||||||
|
const accounts = await web3.eth.getAccounts();
|
||||||
|
const supply = "184467440737095516160000000000";
|
||||||
|
const firstTransfer = "1000000000000";
|
||||||
|
|
||||||
|
// mint and approve tokens
|
||||||
|
const token = new web3.eth.Contract(TokenImplementation.abi, TokenImplementation.address);
|
||||||
|
await token.methods.mint(accounts[0], supply).send({
|
||||||
|
value : 0,
|
||||||
|
from : accounts[0],
|
||||||
|
gasLimit : 2000000
|
||||||
|
});
|
||||||
|
await token.methods.approve(TokenBridge.address, supply).send({
|
||||||
|
value : 0,
|
||||||
|
from : accounts[0],
|
||||||
|
gasLimit : 2000000
|
||||||
|
});
|
||||||
|
|
||||||
|
// deposit tokens
|
||||||
|
const initialized = new web3.eth.Contract(BridgeImplementationFullABI, TokenBridge.address);
|
||||||
|
|
||||||
|
await initialized.methods.transferTokens(
|
||||||
|
TokenImplementation.address,
|
||||||
|
firstTransfer,
|
||||||
|
"10",
|
||||||
|
"0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
).send({
|
||||||
|
value : 0,
|
||||||
|
from : accounts[0],
|
||||||
|
gasLimit : 2000000
|
||||||
|
});
|
||||||
|
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
await initialized.methods.transferTokens(
|
||||||
|
TokenImplementation.address,
|
||||||
|
new BigNumber(supply).minus(firstTransfer).toString(10),
|
||||||
|
"10",
|
||||||
|
"0x000000000000000000000000b7a2211e8165943192ad04f5dd21bedc29ff003e",
|
||||||
|
"0",
|
||||||
|
"0"
|
||||||
|
).send({
|
||||||
|
value : 0,
|
||||||
|
from : accounts[0],
|
||||||
|
gasLimit : 2000000
|
||||||
|
});
|
||||||
|
} catch(error) {
|
||||||
|
assert.equal(error.message, "Returned error: VM Exception while processing transaction: revert transfer exceeds max outstanding bridged token amount")
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.ok(failed)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const signAndEncodeVM = async function (
|
const signAndEncodeVM = async function (
|
||||||
|
|
Loading…
Reference in New Issue