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

fix: normalize transferred tokens by decimals #159

Merged
merged 17 commits into from
Jan 9, 2025
3 changes: 2 additions & 1 deletion evm/src/omni-bridge/contracts/BridgeTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ library BridgeTypes {
string token,
string name,
string symbol,
uint8 decimals
uint8 decimals,
uint8 originDecimals
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need originDecimals? Since we have token we can look it up on Near

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a little bit complicated to retrieve the origin decimals due to async specific of the cross calls on Near, so by adding this we can reduce the number of callbacks on Near side

);

event LogMetadata(
Expand Down
35 changes: 31 additions & 4 deletions evm/src/omni-bridge/contracts/OmniBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,26 @@ contract OmniBridge is
_grantRole(PAUSABLE_ADMIN_ROLE, _msgSender());
}

function addCustomToken(string calldata nearTokenId, address tokenAddress, address customMinter) external onlyRole(DEFAULT_ADMIN_ROLE) {
function addCustomToken(string calldata nearTokenId, address tokenAddress, address customMinter, uint8 originDecimals) external onlyRole(DEFAULT_ADMIN_ROLE) {
isBridgeToken[tokenAddress] = true;
ethToNearToken[tokenAddress] = nearTokenId;
nearToEthToken[nearTokenId] = tokenAddress;
customMinters[tokenAddress] = customMinter;

string memory name = IERC20Metadata(tokenAddress).name();
string memory symbol = IERC20Metadata(tokenAddress).symbol();
uint8 decimals = IERC20Metadata(tokenAddress).decimals();

deployTokenExtension(nearTokenId, tokenAddress, decimals, originDecimals);

emit BridgeTypes.DeployToken(
tokenAddress,
nearTokenId,
name,
symbol,
decimals,
originDecimals
);
}

function removeCustomToken(address tokenAddress) external onlyRole(DEFAULT_ADMIN_ROLE) {
Expand All @@ -90,6 +105,7 @@ contract OmniBridge is
}

require(!isBridgeToken[nearToEthToken[metadata.token]], "ERR_TOKEN_EXIST");
uint8 decimals = _normalizeDecimals(metadata.decimals);

address bridgeTokenProxy = address(
new ERC1967Proxy(
Expand All @@ -98,18 +114,19 @@ contract OmniBridge is
BridgeToken.initialize.selector,
metadata.name,
metadata.symbol,
metadata.decimals
decimals
)
)
);

deployTokenExtension(metadata.token, bridgeTokenProxy);
deployTokenExtension(metadata.token, bridgeTokenProxy, decimals, metadata.decimals);

emit BridgeTypes.DeployToken(
bridgeTokenProxy,
metadata.token,
metadata.name,
metadata.symbol,
decimals,
metadata.decimals
);

Expand All @@ -120,7 +137,7 @@ contract OmniBridge is
return bridgeTokenProxy;
}

function deployTokenExtension(string memory token, address tokenAddress) internal virtual {}
function deployTokenExtension(string memory token, address tokenAddress, uint8 decimals, uint8 originDecimals) internal virtual {}

function setMetadata(
string calldata token,
Expand Down Expand Up @@ -292,6 +309,16 @@ contract OmniBridge is
proxy.upgradeToAndCall(implementation, bytes(""));
}

function _normalizeDecimals(
uint8 decimals
) internal pure returns (uint8) {
uint8 maxAllowedDecimals = 18;
if (decimals > maxAllowedDecimals) {
return maxAllowedDecimals;
}
return decimals;
}

function _authorizeUpgrade(
address newImplementation
) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
Expand Down
6 changes: 4 additions & 2 deletions evm/src/omni-bridge/contracts/OmniBridgeWormhole.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ contract OmniBridgeWormhole is OmniBridge {
_consistencyLevel = consistencyLevel;
}

function deployTokenExtension(string memory token, address tokenAddress) internal override {
function deployTokenExtension(string memory token, address tokenAddress, uint8 decimals, uint8 originDecimals) internal override {
bytes memory payload = bytes.concat(
bytes1(uint8(MessageType.DeployToken)),
Borsh.encodeString(token),
bytes1(omniBridgeChainId),
Borsh.encodeAddress(tokenAddress)
Borsh.encodeAddress(tokenAddress),
bytes1(decimals),
bytes1(originDecimals)
);
_wormhole.publishMessage{value: msg.value}(
wormholeNonce,
Expand Down
1 change: 1 addition & 0 deletions evm/src/omni-bridge/contracts/test/TestWormhole.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ contract TestWormhole {
bytes memory payload,
uint8 consistencyLevel
) external payable returns (uint64) {
require(msg.value == this.messageFee(), "invalid fee");
emit MessagePublished(nonce, payload, consistencyLevel);
return 0;
}
Expand Down
9 changes: 5 additions & 4 deletions evm/tests/BridgeToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const PauseMode = {
PausedFinTransfer: 1 << 1,
}
const PauseAll = PauseMode.PausedInitTransfer | PauseMode.PausedFinTransfer
const PanicCodeArithmeticOperationOverflowed = "0x11"

describe("BridgeToken", () => {
const wrappedNearId = "wrap.testnet"
Expand Down Expand Up @@ -69,7 +70,7 @@ describe("BridgeToken", () => {
const token = OmniBridgeInstance.attach(tokenProxyAddress) as BridgeToken
expect(await token.name()).to.be.equal("Wrapped NEAR fungible token")
expect(await token.symbol()).to.be.equal("wNEAR")
expect((await token.decimals()).toString()).to.be.equal("24")
expect((await token.decimals()).toString()).to.be.equal("18")
})

it("can't create token if token already exists", async () => {
Expand Down Expand Up @@ -290,7 +291,7 @@ describe("BridgeToken", () => {
"testrecipient.near",
message,
),
).to.be.revertedWithCustomError(OmniBridge, "InvalidValue")
).to.be.revertedWithPanic(PanicCodeArithmeticOperationOverflowed)
})

it("can't init transfer when value is too high", async () => {
Expand All @@ -305,7 +306,7 @@ describe("BridgeToken", () => {
const message = ""

await expect(
OmniBridge.initTransfer(
OmniBridge.connect(user1).initTransfer(
tokenProxyAddress,
payload.amount,
fee,
Expand Down Expand Up @@ -362,7 +363,7 @@ describe("BridgeToken", () => {
expect(await BridgeTokenV2Proxied.returnTestString()).to.equal("test")
expect(await BridgeTokenV2Proxied.name()).to.equal("Wrapped NEAR fungible token")
expect(await BridgeTokenV2Proxied.symbol()).to.equal("wNEAR")
expect((await BridgeTokenV2Proxied.decimals()).toString()).to.equal("24")
expect((await BridgeTokenV2Proxied.decimals()).toString()).to.equal("18")
})

it("user can't upgrade token contract", async () => {
Expand Down
16 changes: 9 additions & 7 deletions evm/tests/BridgeTokenWormhole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ethers, upgrades } from "hardhat"
import type { BridgeToken, OmniBridgeWormhole, TestWormhole } from "../typechain-types"
import { depositSignature, metadataSignature, testWallet } from "./helpers/signatures"

const WormholeFee = 10000

class FinTransferWormholeMessage {
static schema = {
struct: {
Expand Down Expand Up @@ -124,7 +126,7 @@ describe("BridgeTokenWormhole", () => {
): Promise<{ tokenProxyAddress: string; token: BridgeToken }> {
const { signature, payload } = metadataSignature(tokenId)

await OmniBridgeWormhole.deployToken(signature, payload)
await OmniBridgeWormhole.deployToken(signature, payload, { value: WormholeFee })
const tokenProxyAddress = await OmniBridgeWormhole.nearToEthToken(tokenId)
const token = OmniBridgeInstance.attach(tokenProxyAddress) as BridgeToken
return { tokenProxyAddress, token }
Expand All @@ -133,7 +135,7 @@ describe("BridgeTokenWormhole", () => {
it("deploy token", async () => {
const { signature, payload } = metadataSignature(wrappedNearId)

await expect(await OmniBridgeWormhole.deployToken(signature, payload))
await expect(await OmniBridgeWormhole.deployToken(signature, payload, { value: WormholeFee }))
.to.emit(TestWormhole, "MessagePublished")
.withArgs(0, anyValue, consistencyLevel)
})
Expand All @@ -154,7 +156,7 @@ describe("BridgeTokenWormhole", () => {
feeRecipient: payload.feeRecipient,
})

await expect(OmniBridgeWormhole.finTransfer(signature, payload))
await expect(OmniBridgeWormhole.finTransfer(signature, payload, { value: WormholeFee }))
.to.emit(TestWormhole, "MessagePublished")
.withArgs(1, messagePayload, consistencyLevel)

Expand All @@ -167,7 +169,7 @@ describe("BridgeTokenWormhole", () => {
const { token } = await createToken(wrappedNearId)
const tokenProxyAddress = await token.getAddress()
const { signature, payload } = depositSignature(tokenProxyAddress, await user1.getAddress())
await OmniBridgeWormhole.finTransfer(signature, payload)
await OmniBridgeWormhole.finTransfer(signature, payload, { value: WormholeFee })

const recipient = "testrecipient.near"
const fee = 0
Expand Down Expand Up @@ -198,7 +200,7 @@ describe("BridgeTokenWormhole", () => {
recipient,
message,
{
value: 10000,
value: WormholeFee,
},
),
)
Expand All @@ -212,7 +214,7 @@ describe("BridgeTokenWormhole", () => {
const { token } = await createToken(wrappedNearId)
const tokenProxyAddress = await token.getAddress()
const { signature, payload } = depositSignature(tokenProxyAddress, await user1.getAddress())
await OmniBridgeWormhole.finTransfer(signature, payload)
await OmniBridgeWormhole.finTransfer(signature, payload, { value: WormholeFee })

await expect(
OmniBridgeWormhole.connect(user1).initTransfer(
Expand All @@ -223,6 +225,6 @@ describe("BridgeTokenWormhole", () => {
"testrecipient.near",
"",
),
).to.be.revertedWithCustomError(OmniBridgeWormhole, "InvalidValue")
).to.be.revertedWith("invalid fee")
})
})
Loading
Loading