Skip to content

Commit

Permalink
fix: normalize transferred tokens by decimals (#159)
Browse files Browse the repository at this point in the history
* fix: normalize transferred tokens by decimals

* Fix naming

* Fix typo

* Fix clippy

* Store normalization dust for zero fee transfers

* Fix tests

* Fix test

* Fix tests

* Fix dust increment

* Fix normalized amount in claim fee

* Remove dust from storage

* Panic if decimals not exist

* fix normalize and denormalize

* Fix tests

* Rename `de_normalize` to `denormalize`

* Fix typo
  • Loading branch information
karim-en authored Jan 9, 2025
1 parent d5d365a commit d54dbc4
Show file tree
Hide file tree
Showing 19 changed files with 335 additions and 46 deletions.
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
);

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

0 comments on commit d54dbc4

Please sign in to comment.