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

Contracts V2 #1300

Open
wants to merge 38 commits into
base: v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e0b2371
v2 initial commit
vgeddes Oct 2, 2024
a7ad609
latest changes
vgeddes Oct 7, 2024
1be9e5c
Flesh out dispatch logic for inbound messages
vgeddes Oct 8, 2024
e7fecaf
Add reward address
vgeddes Oct 10, 2024
08715b0
make code compile
vgeddes Oct 17, 2024
111f738
Split initializer impl into a library to reduce contract size
vgeddes Oct 17, 2024
b0867e8
Major refactor
vgeddes Oct 18, 2024
ee37e3c
Update tests
vgeddes Oct 18, 2024
6829f38
add scripts back
vgeddes Oct 19, 2024
8c878ce
Update scripts
vgeddes Oct 19, 2024
0b25864
Merge branch 'v2' into vincent/v2
vgeddes Oct 19, 2024
c35743d
Finish outbound messaging
vgeddes Oct 19, 2024
149c3e4
Implement token registration for V2
vgeddes Oct 20, 2024
02ac263
Make functions payable
vgeddes Oct 21, 2024
c7332c3
improve docs
vgeddes Oct 21, 2024
c0bab8a
comments
vgeddes Oct 24, 2024
eae219e
review feedback
vgeddes Oct 27, 2024
318b7dc
cleanups
vgeddes Oct 27, 2024
e73125d
Add initial tests for V2
vgeddes Oct 27, 2024
fa9b8a7
Make `rewardAddress` an indexed event parameter
vgeddes Oct 27, 2024
00016a7
Clean up interfaces
vgeddes Nov 3, 2024
5817542
Make WETH address configurable
vgeddes Nov 4, 2024
4c3b2af
Autowrap ether
vgeddes Nov 4, 2024
98b7d51
auto-unwrap ether
vgeddes Nov 10, 2024
553b0b9
Add view keyword
yrong Nov 11, 2024
3a7f56c
improve
vgeddes Dec 7, 2024
dabecbf
improve
vgeddes Dec 7, 2024
8a1e688
allow unlocking native ether
vgeddes Dec 7, 2024
75ad3f5
improve token registration flows
vgeddes Dec 7, 2024
6bac207
improve docs
vgeddes Dec 7, 2024
7b28a11
improve
vgeddes Dec 7, 2024
7378735
improve
vgeddes Dec 7, 2024
4e17456
nit
vgeddes Dec 7, 2024
fc48f45
Update contracts/src/v1/Calls.sol
vgeddes Dec 10, 2024
e35cd91
Apply suggestions from code review
vgeddes Dec 10, 2024
55e6cab
Fix unlock WETH
yrong Dec 8, 2024
005da17
Suppress Error (6243): The "tload" instruction
yrong Dec 11, 2024
d77abee
Merge recent changes
yrong Dec 11, 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
2 changes: 1 addition & 1 deletion contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ coverage/
out/
cache/
broadcast/
types/
/types/
tsconfig.tsbuildinfo
lcov.info
tenderly.yaml
Expand Down
4 changes: 4 additions & 0 deletions contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ via_ir = true

[profile.production.etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }

[fmt]
number_underscore = "thousands"
line_length = 89
10 changes: 10 additions & 0 deletions contracts/src/AgentExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {SubstrateTypes} from "./SubstrateTypes.sol";

import {IERC20} from "./interfaces/IERC20.sol";
import {SafeTokenTransfer, SafeNativeTransfer} from "./utils/SafeTransfer.sol";
import {Call} from "./utils/Call.sol";
import {Gateway} from "./Gateway.sol";

/// @title Code which will run within an `Agent` using `delegatecall`.
Expand All @@ -27,6 +28,15 @@ contract AgentExecutor {
_transferToken(token, recipient, amount);
}

function callContract(address target, bytes data) external {
bool success = Call.safeCall(target, data);
if (!success) {
revert();
}
}

function deposit() external payable {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why empty?


/// @dev Transfer ERC20 to `recipient`. Only callable via `execute`.
function _transferToken(address token, address recipient, uint128 amount) internal {
IERC20(token).safeTransfer(recipient, amount);
Expand Down
File renamed without changes.
196 changes: 196 additions & 0 deletions contracts/src/AssetsV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
pragma solidity 0.8.25;

import {IERC20} from "./interfaces/IERC20.sol";
import {IGateway} from "./interfaces/IGateway.sol";

import {SafeTokenTransferFrom} from "./utils/SafeTransfer.sol";

import {AssetsStorage, TokenInfo} from "./storage/AssetsStorage.sol";
import {CoreStorage} from "./storage/CoreStorage.sol";

import {SubstrateTypes} from "./SubstrateTypes.sol";
import {ParaID, MultiAddress, Ticket, Costs, TransferKind, TicketV2} from "./Types.sol";
import {Address} from "./utils/Address.sol";
import {AgentExecutor} from "./AgentExecutor.sol";
import {Agent} from "./Agent.sol";
import {Call} from "./utils/Call.sol";
import {Token} from "./Token.sol";
import {WETH9} from "canonical-weth/WETH9.sol";

/// @title Library for implementing Ethereum->Polkadot ERC20 transfers.
library Assets {
using Address for address;
using SafeTokenTransferFrom for IERC20;

/* Errors */
error InvalidToken();
error InvalidAmount();
error InvalidDestination();
error TokenNotRegistered();
error Unsupported();
error InvalidDestinationFee();
error AgentDoesNotExist();
error TokenAlreadyRegistered();
error TokenMintFailed();
error TokenTransferFailed();

/*
* _____ __________ .___ ____
* / _ \ \______ \| | ___ __/_ |
* / /_\ \ | ___/| | \ \/ / | |
* / | \ | | | | \ / | |
* \____|__ / |____| |___| \_/ |___|
* \/
*/

function isTokenRegistered(address token) external view returns (bool) {
return AssetsStorage.layout().tokenRegistry[token].isRegistered;
}

/// @dev transfer tokens from the sender to the specified agent
function _transferToAgent(
address agent,
address token,
address sender,
uint128 amount
) internal {
if (!token.isContract()) {
revert InvalidToken();
}

if (amount == 0) {
revert InvalidAmount();
}

IERC20(token).safeTransferFrom(sender, agent, amount);
}

/// @dev Registers a token (only native tokens at this time)
/// @param token The ERC20 token address.
function registerToken(address token) external returns (TicketV2 memory ticket) {}

// @dev Register a new fungible Polkadot token for an agent
function registerForeignToken(
bytes32 foreignTokenID,
string memory name,
string memory symbol,
uint8 decimals
) external {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
if ($.tokenAddressOf[foreignTokenID] != address(0)) {
revert TokenAlreadyRegistered();
}
Token token = new Token(name, symbol, decimals);
TokenInfo memory info =
TokenInfo({isRegistered: true, foreignID: foreignTokenID});

$.tokenAddressOf[foreignTokenID] = address(token);
$.tokenRegistry[address(token)] = info;

emit IGateway.ForeignTokenRegistered(foreignTokenID, address(token));
}

// @dev Mint foreign token from Polkadot
function mintForeignToken(bytes32 foreignTokenID, address recipient, uint256 amount)
external
{
address token = _ensureTokenAddressOf(foreignTokenID);
Token(token).mint(recipient, amount);
}

// @dev Transfer ERC20 to `recipient`
function transferNativeToken(
address executor,
address agent,
address token,
address recipient,
uint128 amount
) external {
bytes memory call =
abi.encodeCall(AgentExecutor.transferToken, (token, recipient, amount));
(bool success,) = Agent(payable(agent)).invoke(executor, call);
if (!success) {
revert TokenTransferFailed();
}
}

// @dev Get token address by tokenID
function tokenAddressOf(bytes32 tokenID) external view returns (address) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
return $.tokenAddressOf[tokenID];
}

// @dev Get token address by tokenID
function _ensureTokenAddressOf(bytes32 tokenID) internal view returns (address) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
if ($.tokenAddressOf[tokenID] == address(0)) {
revert TokenNotRegistered();
}
return $.tokenAddressOf[tokenID];
}

function _isTokenRegistered(address token) internal view returns (bool) {
AssetsStorage.Layout storage $ = AssetsStorage.layout();
return $.tokenRegistry[token].isRegistered;
}

/*
* _____ __________ .___ ________
* / _ \ \______ \| | ___ __\_____ \
* / /_\ \ | ___/| | \ \/ / / ____/§
* / | \ | | | | \ / / \
* \____|__ / |____| |___| \_/ \_______ \
* \/ \/
*/

function sendMessage(bytes calldata xcm, bytes[] calldata assets)
external
returns (TicketV2 memory)
{
AssetsStorage.Layout storage $ = AssetsStorage.layout();

bytes[] memory xfers = new bytes[](assets.length);
for (uint256 i = 0; i < assets.length; i++) {
xfers[i] = _handleAsset(assets[i]);
}

return TicketV2({costs: Costs({foreign: 0, native: 0}), xfers: xfers, xcm: xcm});
}

function _handleAsset(bytes calldata asset) internal returns (bytes memory) {
uint8 assetKind;
assembly {
assetKind := calldataload(asset.offset)
}
if (assetKind == 0) {
(, address token, uint128 amount) =
abi.decode(asset, (uint8, address, uint128));
return _handleAssetERC20(token, amount);
}
}

function _handleAssetERC20(address token, uint128 amount)
internal
returns (bytes memory)
{
AssetsStorage.Layout storage $ = AssetsStorage.layout();
TokenInfo storage info = $.tokenRegistry[token];

if (!info.isRegistered) {
revert TokenNotRegistered();
}

if (info.foreignID == bytes32(0)) {
_transferToAgent($.assetHubAgent, token, msg.sender, amount);
return
SubstrateTypes.encodeTransfer(TransferKind.LocalReserve, token, amount);
} else {
Token(token).burn(msg.sender, amount);
return SubstrateTypes.encodeTransfer(
TransferKind.DestinationReserve, token, amount
);
}
}
}
Loading
Loading