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

Transact from eth to sub #1141

Open
wants to merge 45 commits into
base: bridge-next-gen
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
55cc688
Change for contracts
yrong Feb 7, 2024
07395ce
Convert logic for transact command
yrong Feb 8, 2024
d41bd08
Reuse msg.sender for both originKinds
yrong Feb 13, 2024
16e7cbe
Revert to burn&teleport
yrong Feb 13, 2024
a01aaab
Update contract bindings
yrong Feb 13, 2024
b660ea8
Update sdk
yrong Feb 13, 2024
89feaf3
Merge branch 'main' into ron/transact-from-eth-to-sub
yrong Feb 14, 2024
a94183a
Remove unused
yrong Feb 14, 2024
7a110d3
Add smoke test
yrong Feb 15, 2024
b57f0c1
Improve smoke test
yrong Feb 15, 2024
3ad5b7d
Add polkadot-sdk as soft link
yrong Feb 15, 2024
e93c725
Revert typos config
yrong Feb 15, 2024
65714ef
Add valid check
yrong Feb 16, 2024
4719a42
Remove TransactMessage and inline the params
yrong Feb 19, 2024
a78b1f9
Remove from .gitignore
yrong Feb 19, 2024
c192361
Add quoteTransactFee
yrong Feb 19, 2024
acf2362
Rename to destinationFee
yrong Feb 20, 2024
207460d
Test quoteTransactFee
yrong Feb 20, 2024
f8f0f57
Check call length
yrong Feb 22, 2024
00abc13
Initialize ticket with explicit cost
yrong Feb 22, 2024
5084432
Rename to sendCall
yrong Feb 22, 2024
d7cc7d8
Reuse weight type with compact u64 encode support
yrong Feb 23, 2024
2f3560c
Fix smoke test with sovereign account of the sender
yrong Feb 23, 2024
e99ce53
Fix generate inbound fixture
yrong Feb 26, 2024
2e4140c
Merge branch 'main' into ron/transact-from-eth-to-sub
yrong Feb 29, 2024
dd3378e
Merge branch 'main' into ron/transact-from-eth-to-sub
yrong Mar 6, 2024
3d91da4
Support TransactFeeMode
yrong Mar 6, 2024
ac7e1f5
Merge branch 'main' into ron/transact-from-eth-to-sub
yrong Mar 7, 2024
72f6d3e
Merge branch 'main' into ron/transact-from-eth-to-sub
yrong Mar 19, 2024
68a567f
Remove prefunding mode
yrong Mar 19, 2024
923187e
Merge branch 'main' into ron/transact-from-eth-to-sub
yrong Apr 11, 2024
3dd1adc
Add param checks back
yrong Apr 11, 2024
f07507f
Remove script unused
yrong Apr 11, 2024
70413d8
Remove the fund script not in use
yrong Apr 11, 2024
a364298
Update relayer
yrong Apr 11, 2024
bdf4c71
Merge branch 'bridge-next-gen' into ron/transact-from-eth-to-sub
yrong Apr 16, 2024
748eea4
Add rfc doc
yrong Apr 17, 2024
e27c0eb
Ignore destination fee for the transact
yrong Apr 17, 2024
e5c1a70
Update rfc
yrong Apr 22, 2024
53791d2
Update rfc with fee section
yrong Apr 22, 2024
61d5ea5
Cleanup
yrong Apr 24, 2024
dfa331d
Merge branch 'bridge-next-gen' into ron/transact-from-eth-to-sub
yrong Apr 24, 2024
b935ca8
Update rfc doc
yrong Apr 29, 2024
2847751
Update rfc
yrong May 5, 2024
bfdff26
Remove unused destinationFee
yrong May 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions contracts/src/Assets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ library Assets {
IERC20(token).safeTransferFrom(sender, agent, amount);
}

function sendTokenCosts(address token, ParaID destinationChain, uint128 destinationChainFee, uint128 maxDestinationChainFee)
external
view
returns (Costs memory costs)
{
function sendTokenCosts(
address token,
ParaID destinationChain,
uint128 destinationChainFee,
uint128 maxDestinationChainFee
) external view returns (Costs memory costs) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
TokenInfo storage info = $.tokenRegistry[token];
if (!info.isRegistered) {
Expand Down
40 changes: 38 additions & 2 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import {
Command,
MultiAddress,
Ticket,
Costs
Costs,
OriginKind,
Weight
} from "./Types.sol";
import {Upgrade} from "./Upgrade.sol";
import {IGateway} from "./interfaces/IGateway.sol";
Expand All @@ -29,6 +31,7 @@ import {SafeNativeTransfer} from "./utils/SafeTransfer.sol";
import {Call} from "./utils/Call.sol";
import {Math} from "./utils/Math.sol";
import {ScaleCodec} from "./utils/ScaleCodec.sol";
import {SubstrateTypes} from "./SubstrateTypes.sol";

import {
UpgradeParams,
Expand Down Expand Up @@ -94,6 +97,8 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
error AgentExecutionFailed(bytes returndata);
error InvalidAgentExecutionPayload();
error InvalidConstructorParams();
error AlreadyInitialized();
error InvalidTransact();

// Message handlers can only be dispatched by the gateway itself
modifier onlySelf() {
Expand Down Expand Up @@ -416,7 +421,9 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
uint128 amount
) external payable {
_submitOutbound(
Assets.sendToken(token, msg.sender, destinationChain, destinationAddress, destinationFee, MAX_DESTINATION_FEE, amount)
Assets.sendToken(
token, msg.sender, destinationChain, destinationAddress, destinationFee, MAX_DESTINATION_FEE, amount
)
);
}

Expand Down Expand Up @@ -613,4 +620,33 @@ contract Gateway is IGateway, IInitializable, IUpgradable {
assets.assetHubCreateAssetFee = config.assetHubCreateAssetFee;
assets.assetHubReserveTransferFee = config.assetHubReserveTransferFee;
}

// Calculate cost for transact
function _calculateTransactCost(uint128) internal pure returns (Costs memory costs) {
yrong marked this conversation as resolved.
Show resolved Hide resolved
return Costs({native: 0, foreign: 0});
}

/// @inheritdoc IGateway
function sendCall(
ParaID destinationChain,
OriginKind originKind,
uint128 destinationFee,
Weight calldata weightAtMost,
bytes calldata call
alistair-singh marked this conversation as resolved.
Show resolved Hide resolved
) external payable {
if (call.length == 0 || destinationFee == 0 || weightAtMost.refTime == 0 || weightAtMost.proofSize == 0) {
vgeddes marked this conversation as resolved.
Show resolved Hide resolved
revert InvalidTransact();
}
bytes memory payload =
SubstrateTypes.Transact(msg.sender, originKind.encode(), destinationFee, weightAtMost, call);
Costs memory costs = _calculateTransactCost(destinationFee);
Ticket memory ticket = Ticket({dest: destinationChain, costs: costs, payload: payload});
_submitOutbound(ticket);
}

/// @inheritdoc IGateway
function quoteSendCallFee(uint128 destinationFee) external view returns (uint256) {
Copy link
Contributor

Choose a reason for hiding this comment

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

destinationFee can be removed from here, as the quote should only return delivery fees.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Costs memory costs = _calculateTransactCost(destinationFee);
Copy link
Contributor

Choose a reason for hiding this comment

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

We can remove destinationfee from here because only delivery costs are paid in the new fee model.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

return _calculateFee(costs);
}
}
22 changes: 21 additions & 1 deletion contracts/src/SubstrateTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
pragma solidity 0.8.23;

import {ScaleCodec} from "./utils/ScaleCodec.sol";
import {ParaID} from "./Types.sol";
import {ParaID, Weight, OriginKind} from "./Types.sol";

/**
* @title SCALE encoders for common Substrate types
Expand Down Expand Up @@ -133,4 +133,24 @@ library SubstrateTypes {
ScaleCodec.encodeU128(xcmFee)
);
}

// Arbitrary transact
function Transact(address sender, bytes1 originKind, uint128 fee, Weight memory weight, bytes memory call)
internal
view
returns (bytes memory)
{
return bytes.concat(
bytes1(0x00),
ScaleCodec.encodeU64(uint64(block.chainid)),
bytes1(0x02),
SubstrateTypes.H160(sender),
originKind,
ScaleCodec.encodeU128(fee),
ScaleCodec.encodeCompactU64(weight.refTime),
ScaleCodec.encodeCompactU64(weight.proofSize),
ScaleCodec.checkedEncodeCompactU32(call.length),
call
);
}
}
22 changes: 22 additions & 0 deletions contracts/src/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,25 @@ struct TokenInfo {
bool isRegistered;
bytes31 __padding;
}

struct Weight {
uint64 refTime;
uint64 proofSize;
}

/// @dev OriginKind from https://github.com/Snowfork/polkadot-sdk/blob/348a1a010481002e41594ed75e5d78b7c2dbed92/polkadot/xcm/src/v2/mod.rs#L86
enum OriginKind {
SovereignAccount,
Xcm
}

using {encode} for OriginKind global;

function encode(OriginKind kind) pure returns (bytes1 result) {
if (kind == OriginKind.SovereignAccount) {
result = 0x01;
} else if (kind == OriginKind.Xcm) {
result = 0x03;
}
return result;
}
14 changes: 13 additions & 1 deletion contracts/src/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]>
pragma solidity 0.8.23;

import {OperatingMode, InboundMessage, ParaID, ChannelID, MultiAddress} from "../Types.sol";
import {OperatingMode, InboundMessage, ParaID, ChannelID, MultiAddress, OriginKind, Weight} from "../Types.sol";
import {Verification} from "../Verification.sol";
import {UD60x18} from "prb/math/src/UD60x18.sol";

Expand Down Expand Up @@ -102,4 +102,16 @@ interface IGateway {
uint128 destinationFee,
uint128 amount
) external payable;

vgeddes marked this conversation as resolved.
Show resolved Hide resolved
/// @dev Call transact in destinationChain
function sendCall(
ParaID destinationChain,
OriginKind originKind,
uint128 destinationFee,
Weight calldata weightAtMost,
bytes calldata call
) external payable;

/// @dev Quote a fee in Ether for transact
function quoteSendCallFee(uint128 destinationFee) external view returns (uint256);
}
14 changes: 14 additions & 0 deletions contracts/src/utils/Math.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,18 @@ library Math {
return a - b;
}
}

/**
* @dev Return the count of leading zeros of the uint64 value
*/
function leadingZeros(uint64 val) internal pure returns (uint8) {
unchecked {
uint8 num = 0;
while (val > 0) {
val = val >> 1;
num++;
}
return 64 - num;
}
}
}
20 changes: 20 additions & 0 deletions contracts/src/utils/ScaleCodec.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]>
pragma solidity 0.8.23;

import {Math} from "./Math.sol";

library ScaleCodec {
using Math for uint64;

error UnsupportedCompactEncoding();

uint256 internal constant MAX_COMPACT_ENCODABLE_UINT = 2 ** 30 - 1;
Expand Down Expand Up @@ -126,4 +130,20 @@ library ScaleCodec {
}
return encodeCompactU32(uint32(value));
}

// Supports compact encoding of integers in [0, uint64.MAX]
function encodeCompactU64(uint64 value) internal pure returns (bytes memory) {
if (value <= 2 ** 30 - 1) {
return encodeCompactU32(uint32(value));
} else {
uint8 countOfZeros = value.leadingZeros();
uint8 bytesNeeded = 8 - countOfZeros / 8;
bytes memory result = abi.encodePacked(3 + ((bytesNeeded - 4) << 2));
for (uint8 i = 0; i < bytesNeeded; i++) {
result = bytes.concat(result, abi.encodePacked(uint8(value)));
value >>= 8;
}
return result;
}
}
}
76 changes: 63 additions & 13 deletions contracts/test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {SubstrateTypes} from "./../src/SubstrateTypes.sol";
import {MultiAddress} from "../src/MultiAddress.sol";
import {Channel, InboundMessage, OperatingMode, ParaID, Command, ChannelID, MultiAddress} from "../src/Types.sol";


import {NativeTransferFailed} from "../src/utils/SafeTransfer.sol";
import {PricingStorage} from "../src/storage/PricingStorage.sol";

Expand All @@ -46,21 +45,26 @@ import {
ParaID,
Command,
multiAddressFromBytes32,
multiAddressFromBytes20
multiAddressFromBytes20,
Weight,
OriginKind
} from "../src/Types.sol";

import {WETH9} from "canonical-weth/WETH9.sol";
import {UD60x18, ud60x18, convert} from "prb/math/src/UD60x18.sol";

contract GatewayTest is Test {
ParaID public bridgeHubParaID = ParaID.wrap(1001);
bytes32 public bridgeHubAgentID = keccak256("1001");
ParaID public bridgeHubParaID = ParaID.wrap(1013);
bytes32 public bridgeHubAgentID = keccak256("1013");
address public bridgeHubAgent;

ParaID public assetHubParaID = ParaID.wrap(1002);
bytes32 public assetHubAgentID = keccak256("1002");
ParaID public assetHubParaID = ParaID.wrap(1000);
bytes32 public assetHubAgentID = keccak256("1000");
address public assetHubAgent;

ParaID public penpalParaID = ParaID.wrap(2000);
bytes32 public penpalAgentID = keccak256("2000");

address public relayer;

bytes32[] public proof = [bytes32(0x2f9ee6cfdf244060dc28aa46347c5219e303fc95062dd672b4e406ca5c29764b)];
Expand Down Expand Up @@ -99,12 +103,7 @@ contract GatewayTest is Test {
function setUp() public {
AgentExecutor executor = new AgentExecutor();
gatewayLogic = new GatewayMock(
address(0),
address(executor),
bridgeHubParaID,
bridgeHubAgentID,
foreignTokenDecimals,
maxDestinationFee
address(0), address(executor), bridgeHubParaID, bridgeHubAgentID, foreignTokenDecimals, maxDestinationFee
);
Gateway.Config memory config = Gateway.Config({
mode: OperatingMode.Normal,
Expand All @@ -126,6 +125,14 @@ contract GatewayTest is Test {
bridgeHubAgent = IGateway(address(gateway)).agentOf(bridgeHubAgentID);
assetHubAgent = IGateway(address(gateway)).agentOf(assetHubAgentID);

// Initialize agent/channel for Penpal
GatewayMock(address(gateway)).createAgentPublic(abi.encode(CreateAgentParams({agentID: penpalAgentID})));
GatewayMock(address(gateway)).createChannelPublic(
abi.encode(
CreateChannelParams({channelID: penpalParaID.into(), agentID: penpalAgentID, mode: OperatingMode.Normal})
)
);

// fund the message relayer account
relayer = makeAddr("relayer");

Expand Down Expand Up @@ -857,6 +864,49 @@ contract GatewayTest is Test {
IGateway(address(gateway)).quoteSendTokenFee(address(token), destPara, maxDestinationFee + 1);

vm.expectRevert(Assets.InvalidDestinationFee.selector);
IGateway(address(gateway)).sendToken{value: fee}(address(token), destPara, recipientAddress32, maxDestinationFee + 1, 1);
IGateway(address(gateway)).sendToken{value: fee}(
address(token), destPara, recipientAddress32, maxDestinationFee + 1, 1
);
}

/**
* Transact
*/
function testTransactFromXcmOrigin() public {
bytes memory payload = SubstrateTypes.Transact(account1, 0x03, 1, Weight(1, 1), bytes("0x1"));
console.logBytes(payload);
vm.expectEmit(true, false, false, true);
emit IGateway.OutboundMessageAccepted(penpalParaID.into(), 1, messageID, payload);
hoax(address(account1));
IGateway(address(gateway)).sendCall{value: 1 ether}(penpalParaID, OriginKind.Xcm, 1, Weight(1, 1), bytes("0x1"));
}

function testTransactFromSovereignAccount() public {
bytes memory payload = SubstrateTypes.Transact(account1, 0x01, 1, Weight(1, 1), bytes("0x1"));
console.logBytes(payload);
vm.expectEmit(true, false, false, true);
emit IGateway.OutboundMessageAccepted(penpalParaID.into(), 1, messageID, payload);
hoax(address(account1));
IGateway(address(gateway)).sendCall{value: 1 ether}(
penpalParaID, OriginKind.SovereignAccount, 1, Weight(1, 1), bytes("0x1")
);
}

function testTransactFromSovereignAccountWithFee() public {
bytes memory payload = SubstrateTypes.Transact(account1, 0x01, 1, Weight(1, 1), bytes("0x1"));
console.logBytes(payload);
uint256 fee = IGateway(address(gateway)).quoteSendCallFee(1);
assertEq(fee, 2500000000000000);
vm.expectEmit(true, false, false, true);
emit IGateway.OutboundMessageAccepted(penpalParaID.into(), 1, messageID, payload);
hoax(address(account1));
IGateway(address(gateway)).sendCall{value: fee}(
penpalParaID, OriginKind.SovereignAccount, 1, Weight(1, 1), bytes("0x1")
);
}

function testQuoteSendCallFee() public {
uint256 fee = IGateway(address(gateway)).quoteSendCallFee(1);
assertEq(fee, 2500000000000000);
}
}
12 changes: 12 additions & 0 deletions contracts/test/ScaleCodec.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,16 @@ contract ScaleCodecTest is Test {
vm.expectRevert(ScaleCodec.UnsupportedCompactEncoding.selector);
ScaleCodec.checkedEncodeCompactU32(uint256(type(uint32).max) + 1);
}

function testEncodeCompactU64() public {
assertEq(ScaleCodec.encodeCompactU64(0), hex"00");
Copy link
Contributor

Choose a reason for hiding this comment

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

+1

assertEq(ScaleCodec.encodeCompactU64(63), hex"fc");
assertEq(ScaleCodec.encodeCompactU64(64), hex"0101");
assertEq(ScaleCodec.encodeCompactU64(16383), hex"fdff");
assertEq(ScaleCodec.encodeCompactU64(16384), hex"02000100");
assertEq(ScaleCodec.encodeCompactU64(1073741823), hex"feffffff");
assertEq(ScaleCodec.encodeCompactU64(1073741824), hex"0300000040");
assertEq(ScaleCodec.encodeCompactU64(type(uint32).max), hex"03ffffffff");
assertEq(ScaleCodec.encodeCompactU64(type(uint64).max), hex"13ffffffffffffffff");
}
}
11 changes: 2 additions & 9 deletions contracts/test/mocks/GatewayMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@ contract GatewayMock is Gateway {
uint8 foreignTokenDecimals,
uint128 maxDestinationFee
)
Gateway(
beefyClient,
agentExecutor,
bridgeHubParaID,
bridgeHubHubAgentID,
foreignTokenDecimals,
maxDestinationFee
)
Gateway(beefyClient, agentExecutor, bridgeHubParaID, bridgeHubHubAgentID, foreignTokenDecimals, maxDestinationFee)
{}

function agentExecutePublic(bytes calldata params) external {
Expand Down Expand Up @@ -102,7 +95,7 @@ library AdditionalStorage {
import {IInitializable} from "../../src/interfaces/IInitializable.sol";

// Used to test upgrades.
contract GatewayV2 is IInitializable {
contract GatewayV2 is IInitializable {
// Reinitialize gateway with some additional storage fields
function initialize(bytes memory params) external {
AdditionalStorage.Layout storage $ = AdditionalStorage.layout();
Expand Down
3 changes: 0 additions & 3 deletions go.work

This file was deleted.

Loading
Loading