Skip to content

Commit

Permalink
Merge pull request #12 from pnetwork-association/feat/standalone-adapter
Browse files Browse the repository at this point in the history
Design change: use a single Adapter per each ERC20/xERC20 pairs
  • Loading branch information
gitmp01 authored Jul 26, 2024
2 parents dd1ae96 + bb145c0 commit f571bc2
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 207 deletions.
15 changes: 9 additions & 6 deletions solidity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@
### Fees

- Fees are taken when the asset is wrapped and unwrapped in order to not take them twice
- In spite of this, fees are deposited to a contract called `FeesManager` which is supposed to be deployed on the home(local) chain only
- Consequently, fees are deposited to a contract called `FeesManager` which is supposed to be deployed on the home(local) chain only

## Contracts

### Adapter

Facilitates user/dApp interaction with the XERC20. It exposes two functions and relative variations depending on the asset being swapped:
Note: we use the name Adapter instead of Bridge in order to be consistent with the xERC20 standard jargon.
This component facilitates user/dApp interaction with the xERC20 token. It exposes two functions and relative variations depending on the asset being swapped (native or not native):

- `swap()`: initiate a crosschain transfer of an ERC20/XERC20 token to another chain. The event emitted includes an indexed nonce plus the bytes with the event payload included into a struct called EventBytes.
- `swapNative()`: initiate a crosschain transfer of the native currency to another chain
- `settle()`: finalize the operation created by the swap on the destaintion, it may result into an unwrap operation of the asset if the settlement is done on the home chain (where the lockbox has been deployed) or just a mint operation on the destination chain
- swap(): initiate a cross chain transfer of an ERC20/xERC20 token to another chain. The event emitted includes an indexed nonce plus the event bytes including important information which are going to be verified upon authorization in the settle function.

This is the actual components used to bridge asset crosschains and it will have the minting/burning limits set in the pTokenV2/XERC20 contract.
- settle(): finalise the operation created by the swap event on the destination, it may result in an unwrap operation of the asset if the settlement is done on the home chain (where the lockbox contract has been deployed) or just a mint operation on the destination chain.

This is the actual component used to bridge asset crosschains and it will have the minting/burning limits set in the pTokenV2/XERC20 contract.

Note: the approach taken here requires an Adapter contract deployed for each pair of ERC20/pToken, this is to overcome the need of a registry keeping tracks of assets pairs (see Connext's approach) deployed on each supported chain.

### XERC20Lockbox

Expand Down
4 changes: 1 addition & 3 deletions solidity/script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,18 @@ contract Deploy is Script {
uint256 burningLimit = 1 ether;
address factory = address(0);

XERC20Registry registry = new XERC20Registry();
Adapter adapter = new Adapter(address(registry));
ERC20Test erc20 = new ERC20Test(name, symbol, 1000 ether);
XERC20 xerc20 = new XERC20(
string.concat("p", name),
string.concat("p", symbol),
factory
);

XERC20Lockbox lockbox = new XERC20Lockbox(
address(xerc20),
address(erc20),
false
);
Adapter adapter = new Adapter(address(xerc20), address(erc20));

xerc20.setLockbox(address(lockbox));
xerc20.setLimits(address(adapter), mintingLimit, burningLimit);
Expand Down
54 changes: 19 additions & 35 deletions solidity/src/Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@ contract Adapter is IAdapter, Ownable {

uint256 _nonce;

address public registry;
address public erc20;
address public xerc20;
mapping(bytes32 => bool) public pastEvents;

error NotAllowed();
error InvalidSwap();
error Unauthorized();
error InvalidAmount();
error InvalidSender();
error RLPInputTooLong();
error InvalidOperation();
error InvalidFeesManager();
error InvalidTokenAddress(address token);
error UnsupportedChainId(uint256 chainId);
Expand All @@ -42,18 +45,19 @@ contract Adapter is IAdapter, Ownable {
error InvalidMessageId(uint256 actual, uint256 expected);
error InvalidDestinationChainId(uint256 destinationChainId);

constructor(address registry_) Ownable(msg.sender) {
registry = registry_;
constructor(address _xerc20, address _erc20) Ownable(msg.sender) {
erc20 = _erc20;
xerc20 = _xerc20;
}

/// @inheritdoc IAdapter
// TODO: check reentrancy here
function settle(
Operation memory operation,
IPAM.Metadata calldata metadata
) external {
(, address xerc20) = IXERC20Registry(registry).getAssets(
operation.erc20
);
if (operation.erc20 != bytes32(abi.encode(erc20)))
revert InvalidOperation();

address pam = IXERC20(xerc20).getPAM(address(this));

Expand Down Expand Up @@ -102,8 +106,6 @@ contract Adapter is IAdapter, Ownable {
}

function _finalizeSwap(
bytes32 erc20Bytes,
address xerc20,
uint256 amount,
uint256 destinationChainId,
string memory recipient,
Expand All @@ -130,7 +132,7 @@ contract Adapter is IAdapter, Ownable {
EventBytes(
bytes.concat(
bytes32(_nonce),
erc20Bytes,
bytes32(abi.encode(erc20)),
bytes32(destinationChainId),
bytes32(amount - fees),
bytes32(uint256(uint160(msg.sender))),
Expand All @@ -146,6 +148,7 @@ contract Adapter is IAdapter, Ownable {
}
}

/// @inheritdoc IAdapter
function swap(
address token,
uint256 amount,
Expand All @@ -154,13 +157,10 @@ contract Adapter is IAdapter, Ownable {
bytes memory data
) public {
if (token == address(0)) revert InvalidTokenAddress(token);
if ((token != erc20) && (token != xerc20)) revert NotAllowed();
if (amount <= 0) revert InvalidAmount();

(bytes32 erc20Bytes, address xerc20) = IXERC20Registry(registry)
.getAssets(token);

address lockbox = IXERC20(xerc20).getLockbox();
address erc20 = address(uint160(uint256(erc20Bytes)));

// Native swaps are not allowed within this fn,
// use the swapNative one
Expand All @@ -183,16 +183,10 @@ contract Adapter is IAdapter, Ownable {
IXERC20Lockbox(lockbox).deposit(amount);
}

_finalizeSwap(
erc20Bytes,
xerc20,
amount,
destinationChainId,
recipient,
data
);
_finalizeSwap(amount, destinationChainId, recipient, data);
}

/// @inheritdoc IAdapter
function swap(
address token,
uint256 amount,
Expand All @@ -202,20 +196,16 @@ contract Adapter is IAdapter, Ownable {
swap(token, amount, destinationChainId, recipient, "");
}

/// @inheritdoc IAdapter
function swapNative(
uint256 destinationChainId,
string memory recipient,
bytes memory data
) public payable {
uint256 amount = msg.value;
if (erc20 != address(0)) revert NotAllowed();
if (amount == 0) revert InvalidAmount();

// When wrapping a native asset (i.e. ETH) we map it
// to 32 zero bytes in the registry
(bytes32 erc20, address xerc20) = IXERC20Registry(registry).getAssets(
bytes32(0)
);

address lockbox = IXERC20(xerc20).getLockbox();

// Lockbox must be native here
Expand All @@ -229,16 +219,10 @@ contract Adapter is IAdapter, Ownable {
address(this)
);

_finalizeSwap(
erc20,
xerc20,
amount,
destinationChainId,
recipient,
data
);
_finalizeSwap(amount, destinationChainId, recipient, data);
}

/// @inheritdoc IAdapter
function swapNative(
uint256 destinationChainId,
string memory recipient
Expand Down
30 changes: 28 additions & 2 deletions solidity/src/interfaces/IAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ interface IAdapter {

event Settled();

/**
* Finalise the swap operation on the destination chain
*
* @param operation struct with all the required properties to finalise the swap
* @param metadata struct needed for the PAM in order to verify the operation is legit
*/
function settle(
Operation memory operation,
IPAM.Metadata calldata metadata
Expand All @@ -38,7 +44,7 @@ interface IAdapter {
*
* @dev Be sure the pair is registered in the local XERC20 registry
*
* @param token ERC20 or xERC20 to move across chains
* @param token ERC20 or xERC20 to move across chains (it must be supported by the Adapter)
* @param amount token quantity to move across chains
* @param recipient whom will receive the token
* @param destinationChainId chain id where the wrapped version is destined to
Expand All @@ -56,10 +62,11 @@ interface IAdapter {
*
* @dev Be sure the pair is registered in the local XERC20 registry
*
* @param token ERC20 or xERC20 to move across chains
* @param token ERC20 or xERC20 to move across chains (it must be supported by the Adapter
* @param amount token quantity to move across chains
* @param recipient whom will receive the token
* @param destinationChainId chain id where the wrapped version is destined to
* @param data arbitrary message that would be sent at the end of the settle() function
*
* @dev If the destination chain id doesn't fit in 32 bytes or if there are collisions
* the options are one of the two:
Expand All @@ -76,12 +83,31 @@ interface IAdapter {
bytes memory data
) external;

/**
* Wraps the native currency to another chain
*
* @param destinationChainId chain id where the wrapped version is destined to
* @param recipient whom will receive the token
* @param data arbitrary message that would be sent at the end of the settle() function
*
* @dev The adapter must have the ERC20 address set to addres(0), since there's no ERC20
* token of reference for the native currency.
*/
function swapNative(
uint256 destinationChainId,
string memory recipient,
bytes memory data
) external payable;

/**
* Wraps the native currency to another chain
*
* @param destinationChainId chain id where the wrapped version is destined to
* @param recipient whom will receive the token
*
* @dev The adapter must have the ERC20 address set to addres(0), since there's no ERC20
* token of reference for the native currency.
*/
function swapNative(
uint256 destinationChainId,
string memory recipient
Expand Down
3 changes: 1 addition & 2 deletions solidity/test/forge/Helper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ abstract contract Helper is Test {
vm.chainId(chain);
vm.startPrank(owner);

registry = new XERC20Registry();
adapter = new Adapter(address(registry));
xerc20 = new XERC20("pToken A", "pTKA", factoryAddress);

if (erc20Native == address(0)) {
Expand All @@ -112,6 +110,7 @@ abstract contract Helper is Test {
}
pam = new PAM();
pam.setTeeSigner(signerPublicKey, signerAttestation);
adapter = new Adapter(address(xerc20), address(erc20));
xerc20.setPAM(address(adapter), address(pam));
xerc20.setLimits(address(adapter), mintingLimit, burningLimit);

Expand Down
26 changes: 4 additions & 22 deletions solidity/test/forge/Integration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ contract IntegrationTest is Test, Helper {
userBalance = 50000;
metadata = IPAM.Metadata(
vm.parseBytes(
"0x01010000000000000000000000000000000000000000000000000000000000007a69a880cb2ab67ec9140db0f6de238b34d4108f6fab99315772ee987ef9002e0e6311365bbee18058f12c27236e891a66999c4325879865303f785854e9169c257a0000000000000000000000002946259e0334f33a064106302415ad3391bed384a68959eed8a7e77ce926c4c04ee06434559ae1db7f636ceacd659f5c9126f1c3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051a240271ab8ab9f9a21c82d9a85396b704e164d0000000000000000000000000000000000000000000000000000000000007a6a00000000000000000000000000000000000000000000000000000000000026fc0000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf000000000000000000000000000000000000000000000000000000000000002a307836383133456239333632333732454546363230306633623164624333663831393637316342413639"
"0x01010000000000000000000000000000000000000000000000000000000000007a69a880cb2ab67ec9140db0f6de238b34d4108f6fab99315772ee987ef9002e0e63a880cb2ab67ec9140db0f6de238b34d4108f6fab99315772ee987ef9002e0e630000000000000000000000006d411e0a54382ed43f02410ce1c7a7c122afa6e1a68959eed8a7e77ce926c4c04ee06434559ae1db7f636ceacd659f5c9126f1c300000000000000000000000000000000000000000000000000000000000000000000000000000000000000002946259e0334f33a064106302415ad3391bed3840000000000000000000000000000000000000000000000000000000000007a6a00000000000000000000000000000000000000000000000000000000000026fc0000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf000000000000000000000000000000000000000000000000000000000000002a307836383133456239333632333732454546363230306633623164624333663831393637316342413639"
),
vm.parseBytes(
"0x2ac391f76e0b65c22d954dd83373b14cb90419693607b0520b713d8fb0494e7407ed9c6ec02f7f877e8a297c8d554998e31f8c365388cdd80e4f290dd969fd721c"
"0x387515efe7f640c04214a06d8ec89f2d211c8f197711557c5fff7ac4362543cc05baed1946c41bdeb69cc743319eff3ffa41729e67f4d9b57416f9266a6a69261c"
)
);
}
Expand All @@ -97,24 +97,6 @@ contract IntegrationTest is Test, Helper {
pam_B
) = _setupChain(CHAIN_B, owner, address(erc20_A));

_registerPair(
CHAIN_A,
owner,
owner,
registry_A,
address(erc20_A),
address(xerc20_A)
);

_registerPair(
CHAIN_B,
owner,
owner,
registry_B,
address(erc20_B),
address(xerc20_B)
);

_transferToken(address(erc20_A), owner, user, userBalance);

erc20Bytes_A = bytes32(abi.encode(address(erc20_A)));
Expand Down Expand Up @@ -324,10 +306,10 @@ contract IntegrationTest is Test, Helper {

IPAM.Metadata memory pegoutMetadata = IPAM.Metadata(
vm.parseBytes(
"0x01010000000000000000000000000000000000000000000000000000000000007a6aa880cb2ab67ec9140db0f6de238b34d4108f6fab99315772ee987ef9002e0e6311365bbee18058f12c27236e891a66999c4325879865303f785854e9169c257a00000000000000000000000063f58053c9499e1104a6f6c6d2581d6d83067eeba68959eed8a7e77ce926c4c04ee06434559ae1db7f636ceacd659f5c9126f1c3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051a240271ab8ab9f9a21c82d9a85396b704e164d0000000000000000000000000000000000000000000000000000000000007a6a00000000000000000000000000000000000000000000000000000000000013880000000000000000000000006813eb9362372eef6200f3b1dbc3f819671cba69000000000000000000000000000000000000000000000000000000000000002a307836383133456239333632333732454546363230306633623164624333663831393637316342413639"
"0x01010000000000000000000000000000000000000000000000000000000000007a6aa880cb2ab67ec9140db0f6de238b34d4108f6fab99315772ee987ef9002e0e63a880cb2ab67ec9140db0f6de238b34d4108f6fab99315772ee987ef9002e0e6300000000000000000000000063f58053c9499e1104a6f6c6d2581d6d83067eeba68959eed8a7e77ce926c4c04ee06434559ae1db7f636ceacd659f5c9126f1c300000000000000000000000000000000000000000000000000000000000000000000000000000000000000002946259e0334f33a064106302415ad3391bed3840000000000000000000000000000000000000000000000000000000000007a6a00000000000000000000000000000000000000000000000000000000000013880000000000000000000000006813eb9362372eef6200f3b1dbc3f819671cba69000000000000000000000000000000000000000000000000000000000000002a307836383133456239333632333732454546363230306633623164624333663831393637316342413639"
),
vm.parseBytes(
"0x50e11e1aa6192fa507cd01087106d799642cf431a73640e4ae7219d970f36418645c9cba3613fe4a396a99366598eaed8684700850466d1736b3590ae5a302b91b"
"0xf8f5739485a5567060d29db7c923080313f65ef0070ce942b4a3fd32c0747fd0253d22b56af8c251bc79b9014057eddc24ec4bdd1951bc75ca2ee38e09fe55f21b"
)
);

Expand Down
Loading

0 comments on commit f571bc2

Please sign in to comment.