evm: token registration changes (#21)
* evm: add sanity check to token registration process * evm: fetch target token address from circle TokenMinter contract * evm: remove token registration process --------- Co-authored-by: gator-boi <gator-boi@users.noreply.github.com>
This commit is contained in:
parent
5af729041b
commit
565d4abdfa
|
@ -70,10 +70,6 @@ function updateWormholeFinality(bytes attestedGovernanceMessage)
|
|||
|
||||
function registerEmitterAndDomain(bytes attestedGovernanceMessage)
|
||||
|
||||
function registerAcceptedToken(bytes attestedGovernanceMessage)
|
||||
|
||||
function registerTargetChainToken(bytes attestedGovernanceMessage)
|
||||
|
||||
function upgradeContract(bytes attestedGovernanceMessage)
|
||||
|
||||
function verifyGovernanceMessage(bytes attestedGovernanceMessage, uint8 action)
|
||||
|
|
|
@ -25,9 +25,7 @@ contract CircleIntegrationTest is Test {
|
|||
|
||||
uint8 constant GOVERNANCE_UPDATE_WORMHOLE_FINALITY = 1;
|
||||
uint8 constant GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN = 2;
|
||||
uint8 constant GOVERNANCE_REGISTER_ACCEPTED_TOKEN = 3;
|
||||
uint8 constant GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN = 4;
|
||||
uint8 constant GOVERNANCE_UPGRADE_CONTRACT = 5;
|
||||
uint8 constant GOVERNANCE_UPGRADE_CONTRACT = 3;
|
||||
|
||||
// USDC
|
||||
IUSDC usdc;
|
||||
|
@ -111,20 +109,6 @@ contract CircleIntegrationTest is Test {
|
|||
foreignUsdc = bytes32(uint256(uint160(vm.envAddress("TESTING_FOREIGN_USDC_TOKEN_ADDRESS"))));
|
||||
}
|
||||
|
||||
function registerToken(address token) public {
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_ACCEPTED_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(bytes12(0), token)
|
||||
);
|
||||
|
||||
// Register and should now be accepted.
|
||||
circleIntegration.registerAcceptedToken(encodedMessage);
|
||||
}
|
||||
|
||||
function registerContract(uint16 foreignChain, bytes32 foreignEmitter, uint32 domain) public {
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
|
@ -140,9 +124,6 @@ contract CircleIntegrationTest is Test {
|
|||
}
|
||||
|
||||
function prepareCircleIntegrationTest(uint256 amount) public {
|
||||
// Register USDC with CircleIntegration
|
||||
registerToken(address(usdc));
|
||||
|
||||
// Set up USDC token for test
|
||||
if (amount > 0) {
|
||||
// First mint USDC.
|
||||
|
@ -654,293 +635,6 @@ contract CircleIntegrationTest is Test {
|
|||
}
|
||||
}
|
||||
|
||||
function testCannotRegisterTargetChainTokenInvalidLength(
|
||||
address sourceToken,
|
||||
uint16 targetChain,
|
||||
bytes32 targetToken
|
||||
) public {
|
||||
vm.assume(sourceToken != address(0));
|
||||
vm.assume(targetChain > 0 && targetChain != circleIntegration.chainId());
|
||||
vm.assume(targetToken != bytes32(0));
|
||||
|
||||
// First register source token
|
||||
registerToken(sourceToken);
|
||||
|
||||
// Should not already exist.
|
||||
assertEq(
|
||||
circleIntegration.targetAcceptedToken(sourceToken, targetChain),
|
||||
bytes32(0),
|
||||
"target token already registered"
|
||||
);
|
||||
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(bytes12(0), sourceToken, targetChain, targetToken, "But wait! There's more.")
|
||||
);
|
||||
|
||||
// Now register target token.
|
||||
vm.expectRevert("invalid governance payload length");
|
||||
circleIntegration.registerTargetChainToken(encodedMessage);
|
||||
}
|
||||
|
||||
function testCannotRegisterAcceptedTokenInvalidLength(address tokenAddress) public {
|
||||
vm.assume(tokenAddress != address(0));
|
||||
|
||||
// Should not already be accepted.
|
||||
assertTrue(!circleIntegration.isAcceptedToken(tokenAddress), "token already registered");
|
||||
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_ACCEPTED_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(bytes12(0), tokenAddress, "But wait! There's more.")
|
||||
);
|
||||
|
||||
// Register and should now be accepted.
|
||||
vm.expectRevert("invalid governance payload length");
|
||||
circleIntegration.registerAcceptedToken(encodedMessage);
|
||||
}
|
||||
|
||||
function testCannotRegisterAcceptedTokenZeroAddress() public {
|
||||
// Should not already be accepted.
|
||||
address tokenAddress = address(0);
|
||||
assertTrue(!circleIntegration.isAcceptedToken(tokenAddress), "token already registered");
|
||||
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_ACCEPTED_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(bytes12(0), tokenAddress)
|
||||
);
|
||||
|
||||
// You shall not pass!
|
||||
vm.expectRevert("token is zero address");
|
||||
circleIntegration.registerAcceptedToken(encodedMessage);
|
||||
}
|
||||
|
||||
function testCannotRegisterAcceptedTokenInvalidToken(bytes12 garbage, address tokenAddress) public {
|
||||
vm.assume(garbage != bytes12(0));
|
||||
vm.assume(tokenAddress != address(0));
|
||||
|
||||
// Should not already be accepted.
|
||||
assertTrue(!circleIntegration.isAcceptedToken(tokenAddress), "token already registered");
|
||||
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_ACCEPTED_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(garbage, tokenAddress)
|
||||
);
|
||||
|
||||
// You shall not pass!
|
||||
vm.expectRevert("invalid address");
|
||||
circleIntegration.registerAcceptedToken(encodedMessage);
|
||||
}
|
||||
|
||||
function testRegisterAcceptedToken(address tokenAddress) public {
|
||||
vm.assume(tokenAddress != address(0));
|
||||
|
||||
// Should not already be accepted.
|
||||
assertTrue(!circleIntegration.isAcceptedToken(tokenAddress), "token already registered");
|
||||
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_ACCEPTED_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(bytes12(0), tokenAddress)
|
||||
);
|
||||
|
||||
// Register and should now be accepted.
|
||||
circleIntegration.registerAcceptedToken(encodedMessage);
|
||||
|
||||
assertTrue(circleIntegration.isAcceptedToken(tokenAddress), "token not registered");
|
||||
}
|
||||
|
||||
function testCannotRegisterTargetChainTokenInvalidSourceToken(
|
||||
bytes12 garbage,
|
||||
address sourceToken,
|
||||
uint16 targetChain,
|
||||
bytes32 targetToken
|
||||
) public {
|
||||
vm.assume(garbage != bytes12(0));
|
||||
vm.assume(sourceToken != address(0));
|
||||
vm.assume(targetChain > 0 && targetChain != circleIntegration.chainId());
|
||||
vm.assume(targetToken != bytes32(0));
|
||||
|
||||
// Should not already exist.
|
||||
assertEq(
|
||||
circleIntegration.targetAcceptedToken(sourceToken, targetChain),
|
||||
bytes32(0),
|
||||
"target token already registered"
|
||||
);
|
||||
|
||||
// First attempt to submit garbage source token
|
||||
{
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(garbage, sourceToken, targetChain, targetToken)
|
||||
);
|
||||
|
||||
// You shall not pass!
|
||||
vm.expectRevert("invalid address");
|
||||
circleIntegration.registerTargetChainToken(encodedMessage);
|
||||
}
|
||||
|
||||
// Now use legitimate-looking ERC20 address
|
||||
{
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(bytes12(0), sourceToken, targetChain, targetToken)
|
||||
);
|
||||
|
||||
// You shall not pass!
|
||||
vm.expectRevert("source token not accepted");
|
||||
circleIntegration.registerTargetChainToken(encodedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function testCannotRegisterTargetChainTokenInvalidTargetChain(address sourceToken, bytes32 targetToken) public {
|
||||
vm.assume(sourceToken != address(0));
|
||||
vm.assume(targetToken != bytes32(0));
|
||||
|
||||
// First register source token
|
||||
registerToken(sourceToken);
|
||||
|
||||
// Cannot register chain ID == 0
|
||||
{
|
||||
uint16 targetChain = 0;
|
||||
|
||||
// Should not already exist.
|
||||
assertEq(
|
||||
circleIntegration.targetAcceptedToken(sourceToken, targetChain),
|
||||
bytes32(0),
|
||||
"target token already registered"
|
||||
);
|
||||
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(bytes12(0), sourceToken, targetChain, targetToken)
|
||||
);
|
||||
|
||||
// You shall not pass!
|
||||
vm.expectRevert("invalid target chain");
|
||||
circleIntegration.registerTargetChainToken(encodedMessage);
|
||||
}
|
||||
|
||||
// Cannot register chain ID == this chain's
|
||||
{
|
||||
uint16 targetChain = circleIntegration.chainId();
|
||||
|
||||
// Should not already exist.
|
||||
assertEq(
|
||||
circleIntegration.targetAcceptedToken(sourceToken, targetChain),
|
||||
bytes32(0),
|
||||
"target token already registered"
|
||||
);
|
||||
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(bytes12(0), sourceToken, targetChain, targetToken)
|
||||
);
|
||||
|
||||
// You shall not pass!
|
||||
vm.expectRevert("invalid target chain");
|
||||
circleIntegration.registerTargetChainToken(encodedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function testCannotRegisterTargetChainTokenInvalidTargetToken(address sourceToken, uint16 targetChain) public {
|
||||
vm.assume(sourceToken != address(0));
|
||||
vm.assume(targetChain > 0 && targetChain != circleIntegration.chainId());
|
||||
|
||||
// First register source token
|
||||
registerToken(sourceToken);
|
||||
|
||||
// Should not already exist.
|
||||
assertEq(
|
||||
circleIntegration.targetAcceptedToken(sourceToken, targetChain),
|
||||
bytes32(0),
|
||||
"target token already registered"
|
||||
);
|
||||
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(
|
||||
bytes12(0),
|
||||
sourceToken,
|
||||
targetChain,
|
||||
bytes32(0) // targetToken
|
||||
)
|
||||
);
|
||||
|
||||
// You shall not pass!
|
||||
vm.expectRevert("target token is zero address");
|
||||
circleIntegration.registerTargetChainToken(encodedMessage);
|
||||
}
|
||||
|
||||
function testRegisterTargetChainToken(address sourceToken, uint16 targetChain, bytes32 targetToken) public {
|
||||
vm.assume(sourceToken != address(0));
|
||||
vm.assume(targetChain > 0 && targetChain != circleIntegration.chainId());
|
||||
vm.assume(targetToken != bytes32(0));
|
||||
|
||||
// First register source token
|
||||
registerToken(sourceToken);
|
||||
|
||||
// Should not already exist.
|
||||
assertEq(
|
||||
circleIntegration.targetAcceptedToken(sourceToken, targetChain),
|
||||
bytes32(0),
|
||||
"target token already registered"
|
||||
);
|
||||
|
||||
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
|
||||
wormholeSimulator.governanceChainId(),
|
||||
wormholeSimulator.governanceContract(),
|
||||
GOVERNANCE_MODULE,
|
||||
GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN,
|
||||
circleIntegration.chainId(),
|
||||
abi.encodePacked(bytes12(0), sourceToken, targetChain, targetToken)
|
||||
);
|
||||
|
||||
// Now register target token.
|
||||
circleIntegration.registerTargetChainToken(encodedMessage);
|
||||
assertEq(
|
||||
circleIntegration.targetAcceptedToken(sourceToken, targetChain), targetToken, "target token not registered"
|
||||
);
|
||||
}
|
||||
|
||||
function testCannotUpgradeContractInvalidImplementation(bytes12 garbage, address newImplementation) public {
|
||||
vm.assume(garbage != bytes12(0));
|
||||
vm.assume(newImplementation != address(0) && !circleIntegration.isInitialized(newImplementation));
|
||||
|
@ -1086,129 +780,4 @@ contract CircleIntegrationTest is Test {
|
|||
abi.encodePacked("All your base are belong to us") // payload
|
||||
);
|
||||
}
|
||||
|
||||
// function testTransferTokensWithPayload(uint256 amount, uint16 targetChain, bytes32 mintRecipient) public {
|
||||
// vm.assume(amount > 0 && amount <= maxUSDCAmountToMint());
|
||||
// vm.assume(targetChain > 0 && targetChain != circleIntegration.chainId());
|
||||
// vm.assume(mintRecipient != bytes32(0));
|
||||
|
||||
// registerContract(
|
||||
// targetChain,
|
||||
// 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef, // foreignEmitter
|
||||
// 1 // domain
|
||||
// );
|
||||
|
||||
// prepareCircleIntegrationTest(amount);
|
||||
|
||||
// // Register target token
|
||||
// circleIntegration.registerTargetChainToken(
|
||||
// address(usdc), // sourceToken
|
||||
// targetChain,
|
||||
// foreignUsdc // targetToken
|
||||
// );
|
||||
|
||||
// // Record balance.
|
||||
// uint256 myBalanceBefore = usdc.balanceOf(address(this));
|
||||
// assertEq(usdc.balanceOf(address(circleIntegration)), 0, "CircleIntegration has balance");
|
||||
|
||||
// bytes memory payload = abi.encodePacked("All your base are belong to us");
|
||||
|
||||
// vm.recordLogs();
|
||||
|
||||
// // Pass.
|
||||
// circleIntegration.transferTokensWithPayload(address(usdc), amount, targetChain, mintRecipient, payload);
|
||||
|
||||
// // Prepare to check transaction logs for expected events.
|
||||
// Vm.Log[] memory entries = vm.getRecordedLogs();
|
||||
|
||||
// // Circle's MessageSent value
|
||||
// bytes memory message = circleSimulator.findMessageSentInLogs(entries);
|
||||
|
||||
// // Wormhole's LogMessagePublished values
|
||||
// (uint64 sequence, uint32 batchId, bytes memory wormholePayload, uint8 finality) =
|
||||
// wormholeSimulator.findLogMessagePublishedInLogs(entries);
|
||||
// assertEq(sequence, 0, "sequence != expected");
|
||||
// assertEq(batchId, 0, "batchId != expected");
|
||||
// assertEq(finality, circleIntegration.wormholeFinality(), "finality != circleIntegration.wormholeFinality()");
|
||||
|
||||
// // Deserialize wormhole payload
|
||||
// CircleIntegrationSimulator.DepositWithPayload memory deposit =
|
||||
// circleSimulator.decodeDepositWithPayload(wormholePayload);
|
||||
// assertEq(
|
||||
// deposit.token,
|
||||
// circleIntegration.targetAcceptedToken(address(usdc), targetChain),
|
||||
// "deposit.token != expected"
|
||||
// );
|
||||
// assertEq(deposit.amount, amount, "deposit.amount != expected");
|
||||
// assertEq(deposit.sourceDomain, circleIntegration.localDomain(), "deposit.sourceDomain != expected");
|
||||
// assertEq(
|
||||
// deposit.targetDomain,
|
||||
// circleIntegration.getDomainFromChainId(targetChain),
|
||||
// "deposit.targetDomain != expected"
|
||||
// );
|
||||
// assertEq(deposit.nonce, 112396, "deposit.nonce != expected");
|
||||
// assertEq(deposit.mintRecipient, mintRecipient, "deposit.mintRecipient != expected");
|
||||
// assertEq(deposit.payload, payload, "deposit.payload != expected");
|
||||
|
||||
// // My balance change should equal the amount transferred.
|
||||
// assertEq(myBalanceBefore - usdc.balanceOf(address(this)), amount, "mismatch in my balance");
|
||||
|
||||
// // CircleIntegration's balance should not reflect having any USDC.
|
||||
// assertEq(usdc.balanceOf(address(circleIntegration)), 0, "CircleIntegration has new balance");
|
||||
// }
|
||||
|
||||
// function borkedTestRedeemTokensWithPayload(uint16 foreignChain) public {
|
||||
// vm.assume(foreignChain > 0 && foreignChain != circleIntegration.chainId());
|
||||
|
||||
// uint32 foreignDomain = 1;
|
||||
// // Register foreign CircleIntegration
|
||||
// registerContract(
|
||||
// foreignChain,
|
||||
// bytes32(uint256(uint160(address(circleIntegration)))), // foreignEmitter
|
||||
// foreignDomain // domain
|
||||
// );
|
||||
|
||||
// uint256 amount = 42069;
|
||||
// uint64 availableNonce = uint64(vm.envUint("TESTING_LAST_NONCE"));
|
||||
|
||||
// ICircleIntegration.RedeemParameters memory redeemParams;
|
||||
|
||||
// redeemParams.circleBridgeMessage = abi.encodePacked(
|
||||
// messageTransmitter.version(),
|
||||
// foreignDomain,
|
||||
// circleIntegration.localDomain(),
|
||||
// availableNonce,
|
||||
// circleBridge.remoteCircleBridges(foreignDomain),
|
||||
// bytes32(uint256(uint160(address(circleBridge)))),
|
||||
// circleIntegration.getRegisteredEmitter(foreignChain), // expected caller
|
||||
// bytes4(0), // ???
|
||||
// foreignUsdc,
|
||||
// bytes32(uint256(uint160(address(this)))), // attester
|
||||
// amount
|
||||
// );
|
||||
// redeemParams.circleAttestation = circleSimulator.attestMessage(redeemParams.circleBridgeMessage);
|
||||
|
||||
// IWormhole.VM memory wormholeMessage;
|
||||
// wormholeMessage.timestamp = uint32(block.timestamp);
|
||||
// wormholeMessage.nonce = 0;
|
||||
// wormholeMessage.emitterChainId = foreignChain;
|
||||
// wormholeMessage.emitterAddress = bytes32(uint256(uint160(address(circleIntegration))));
|
||||
// wormholeMessage.sequence = 0;
|
||||
// wormholeMessage.consistencyLevel = 1;
|
||||
// wormholeMessage.payload = circleSimulator.encodeDepositWithPayload(
|
||||
// CircleIntegrationStructs.DepositWithPayload({
|
||||
// token: foreignUsdc,
|
||||
// amount: amount,
|
||||
// sourceDomain: foreignDomain,
|
||||
// targetDomain: circleIntegration.localDomain(),
|
||||
// nonce: availableNonce,
|
||||
// fromAddress: bytes32(uint256(uint160(address(this)))),
|
||||
// mintRecipient: bytes32(uint256(uint160(address(this)))),
|
||||
// payload: abi.encodePacked("All your base are belong to us")
|
||||
// })
|
||||
// );
|
||||
// redeemParams.encodedWormholeMessage = wormholeSimulator.signDevnetObservation(wormholeMessage);
|
||||
|
||||
// circleIntegration.redeemTokensWithPayload(redeemParams);
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ fi
|
|||
# ethereum goerli testnet
|
||||
anvil \
|
||||
-m "myth like bonus scare over problem client lizard pioneer submit female collect" \
|
||||
--port 8545 \
|
||||
--port 8546 \
|
||||
--fork-url $ETH_FORK_RPC > anvil_eth.log &
|
||||
|
||||
# avalanche fuji testnet
|
||||
anvil \
|
||||
-m "myth like bonus scare over problem client lizard pioneer submit female collect" \
|
||||
--port 8546 \
|
||||
--port 8547 \
|
||||
--fork-url $AVAX_FORK_RPC > anvil_avax.log &
|
||||
|
||||
sleep 2
|
||||
|
@ -33,19 +33,19 @@ echo "deploy contracts"
|
|||
RELEASE_WORMHOLE_ADDRESS=$ETH_WORMHOLE_ADDRESS \
|
||||
RELEASE_CIRCLE_BRIDGE_ADDRESS=$ETH_CIRCLE_BRIDGE_ADDRESS \
|
||||
forge script $EVM_ROOT/forge-scripts/deploy_contracts.sol \
|
||||
--rpc-url http://localhost:8545 \
|
||||
--rpc-url http://localhost:8546 \
|
||||
--private-key $PRIVATE_KEY \
|
||||
--broadcast --slow > deploy.out 2>&1
|
||||
|
||||
RELEASE_WORMHOLE_ADDRESS=$AVAX_WORMHOLE_ADDRESS \
|
||||
RELEASE_CIRCLE_BRIDGE_ADDRESS=$AVAX_CIRCLE_BRIDGE_ADDRESS \
|
||||
forge script $EVM_ROOT/forge-scripts/deploy_contracts.sol \
|
||||
--rpc-url http://localhost:8546 \
|
||||
--rpc-url http://localhost:8547 \
|
||||
--private-key $PRIVATE_KEY \
|
||||
--broadcast --slow >> deploy.out 2>&1
|
||||
|
||||
forge script $EVM_ROOT/forge-scripts/deploy_mock_contracts.sol \
|
||||
--rpc-url http://localhost:8546 \
|
||||
--rpc-url http://localhost:8547 \
|
||||
--private-key $PRIVATE_KEY \
|
||||
--broadcast --slow >> deploy.out 2>&1
|
||||
|
||||
|
|
|
@ -62,14 +62,14 @@ contract CircleIntegration is CircleIntegrationMessages, CircleIntegrationGovern
|
|||
|
||||
// Call the circle bridge and `depositForBurnWithCaller`. The `mintRecipient`
|
||||
// should be the target contract (or wallet) composing on this contract.
|
||||
(bytes32 targetToken, uint64 nonce, uint256 amountReceived) = _transferTokens(
|
||||
(uint64 nonce, uint256 amountReceived) = _transferTokens(
|
||||
transferParams.token, transferParams.amount, transferParams.targetChain, transferParams.mintRecipient
|
||||
);
|
||||
|
||||
// encode DepositWithPayload message
|
||||
bytes memory encodedMessage = encodeDepositWithPayload(
|
||||
DepositWithPayload({
|
||||
token: targetToken,
|
||||
token: addressToBytes32(transferParams.token),
|
||||
amount: amountReceived,
|
||||
sourceDomain: localDomain(),
|
||||
targetDomain: getDomainFromChainId(transferParams.targetChain),
|
||||
|
@ -86,7 +86,7 @@ contract CircleIntegration is CircleIntegrationMessages, CircleIntegrationGovern
|
|||
|
||||
function _transferTokens(address token, uint256 amount, uint16 targetChain, bytes32 mintRecipient)
|
||||
internal
|
||||
returns (bytes32 targetToken, uint64 nonce, uint256 amountReceived)
|
||||
returns (uint64 nonce, uint256 amountReceived)
|
||||
{
|
||||
// sanity check user input
|
||||
require(amount > 0, "amount must be > 0");
|
||||
|
@ -94,10 +94,6 @@ contract CircleIntegration is CircleIntegrationMessages, CircleIntegrationGovern
|
|||
require(isAcceptedToken(token), "token not accepted");
|
||||
require(getRegisteredEmitter(targetChain) != bytes32(0), "target contract not registered");
|
||||
|
||||
// confirm that the target token was registered
|
||||
targetToken = targetAcceptedToken(token, targetChain);
|
||||
require(targetToken != bytes32(0), "target token not registered");
|
||||
|
||||
// take custody of tokens
|
||||
amountReceived = custodyTokens(token, amount);
|
||||
|
||||
|
@ -166,8 +162,13 @@ contract CircleIntegration is CircleIntegrationMessages, CircleIntegrationGovern
|
|||
// verify the wormhole message
|
||||
IWormhole.VM memory verifiedMessage = verifyWormholeRedeemMessage(params.encodedWormholeMessage);
|
||||
|
||||
// decode the message payload into the DepositWithPayload struct
|
||||
// Decode the message payload into the DepositWithPayload struct. Call the Circle TokenMinter
|
||||
// contract to determine the address of the encoded token on this chain.
|
||||
depositInfo = decodeDepositWithPayload(verifiedMessage.payload);
|
||||
depositInfo.token = fetchLocalTokenAddress(depositInfo.sourceDomain, depositInfo.token);
|
||||
|
||||
// confirm that circle gave us a valid token address
|
||||
require(depositInfo.token != bytes32(0), "invalid local token address");
|
||||
|
||||
// confirm that the caller is the `mintRecipient` to ensure atomic execution
|
||||
require(addressToBytes32(msg.sender) == depositInfo.mintRecipient, "caller must be mintRecipient");
|
||||
|
@ -227,6 +228,25 @@ contract CircleIntegration is CircleIntegrationMessages, CircleIntegrationGovern
|
|||
return (sourceDomain == circleSourceDomain && targetDomain == circleTargetDomain && nonce == circleNonce);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Fetches the local token address given an address and domain from
|
||||
* a different chain.
|
||||
* @param sourceDomain Circle domain for the sending chain.
|
||||
* @param sourceToken Address of the token for the sending chain.
|
||||
* @return Address bytes32 formatted address of the `sourceToken` on this chain.
|
||||
*/
|
||||
function fetchLocalTokenAddress(uint32 sourceDomain, bytes32 sourceToken)
|
||||
public
|
||||
view
|
||||
returns (bytes32)
|
||||
{
|
||||
return addressToBytes32(
|
||||
circleTokenMinter().remoteTokensToLocalTokens(
|
||||
keccak256(abi.encodePacked(sourceDomain, sourceToken))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Converts type address to bytes32 (left-zero-padded)
|
||||
* @param address_ Address to convert to bytes32
|
||||
|
|
|
@ -4,6 +4,7 @@ pragma solidity ^0.8.13;
|
|||
import {IWormhole} from "wormhole/interfaces/IWormhole.sol";
|
||||
import {ICircleBridge} from "../interfaces/circle/ICircleBridge.sol";
|
||||
import {IMessageTransmitter} from "../interfaces/circle/IMessageTransmitter.sol";
|
||||
import {ITokenMinter} from "../interfaces/circle/ITokenMinter.sol";
|
||||
|
||||
import {CircleIntegrationSetters} from "./CircleIntegrationSetters.sol";
|
||||
|
||||
|
@ -57,6 +58,14 @@ contract CircleIntegrationGetters is CircleIntegrationSetters {
|
|||
return IMessageTransmitter(_state.circleTransmitterAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Circle Token Minter contract interface
|
||||
* @return ITokenMinter interface
|
||||
*/
|
||||
function circleTokenMinter() public view returns (ITokenMinter) {
|
||||
return ITokenMinter(_state.circleTokenMinterAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Registered Circle Integration contracts on other blockchains
|
||||
* @param emitterChainId Wormhole chain ID for message sender
|
||||
|
@ -68,21 +77,11 @@ contract CircleIntegrationGetters is CircleIntegrationSetters {
|
|||
|
||||
/**
|
||||
* @notice Circle Bridge registered token boolean
|
||||
* @param token Address of token being checked against `acceptedTokens` state variable
|
||||
* @param token Address of token being checked against the Circle TokenMinter
|
||||
* @return AcceptedToken bool
|
||||
*/
|
||||
function isAcceptedToken(address token) public view returns (bool) {
|
||||
return _state.acceptedTokens[token];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Circle Bridge registered token on target blockchains boolean
|
||||
* @param sourceToken Address of token on this chain
|
||||
* @param chainId_ Wormhole chain ID of target chain
|
||||
* @return TargetAcceptedToken bytes32
|
||||
*/
|
||||
function targetAcceptedToken(address sourceToken, uint16 chainId_) public view returns (bytes32) {
|
||||
return _state.targetAcceptedTokens[sourceToken][chainId_];
|
||||
return circleTokenMinter().burnLimitsPerMessage(token) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,16 +39,8 @@ contract CircleIntegrationGovernance is CircleIntegrationGetters, ERC1967Upgrade
|
|||
uint8 constant GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN = 2;
|
||||
uint256 constant GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN_LENGTH = 73;
|
||||
|
||||
// for registering Circle Bridge supported assets on this chain
|
||||
uint8 constant GOVERNANCE_REGISTER_ACCEPTED_TOKEN = 3;
|
||||
uint256 constant GOVERNANCE_REGISTER_ACCEPTED_TOKEN_LENGTH = 67;
|
||||
|
||||
// for registering Circle Bridge supported assets on other blockchains
|
||||
uint8 constant GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN = 4;
|
||||
uint256 constant GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN_LENGTH = 101;
|
||||
|
||||
// for upgrading implementation (logic) contracts
|
||||
uint8 constant GOVERNANCE_UPGRADE_CONTRACT = 5;
|
||||
uint8 constant GOVERNANCE_UPGRADE_CONTRACT = 3;
|
||||
uint256 constant GOVERNANCE_UPGRADE_CONTRACT_LENGTH = 67;
|
||||
|
||||
/**
|
||||
|
@ -115,62 +107,6 @@ contract CircleIntegrationGovernance is CircleIntegrationGetters, ERC1967Upgrade
|
|||
setDomainToChainId(domain, emitterChainId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice `registerAcceptedToken` registers tokens that can be burned + minted
|
||||
* via the Circle Bridge.
|
||||
* @param encodedMessage Attested Wormhole governance message with the following
|
||||
* relevant fields:
|
||||
* - Field Bytes Type Index
|
||||
* - sourceToken 32 bytes32 35
|
||||
*/
|
||||
function registerAcceptedToken(bytes memory encodedMessage) public {
|
||||
bytes memory payload = verifyAndConsumeGovernanceMessage(encodedMessage, GOVERNANCE_REGISTER_ACCEPTED_TOKEN);
|
||||
require(payload.length == GOVERNANCE_REGISTER_ACCEPTED_TOKEN_LENGTH, "invalid governance payload length");
|
||||
|
||||
// registering accepted tokens should only be relevant for this contract's chain ID
|
||||
require(payload.toUint16(33) == chainId(), "invalid target chain");
|
||||
|
||||
// token at byte 35 (32 bytes, but last 20 is the address)
|
||||
address token = readAddressFromBytes32(payload, 35);
|
||||
require(token != address(0), "token is zero address");
|
||||
|
||||
// update the acceptedTokens mapping
|
||||
addAcceptedToken(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice `registerTargetChainToken` registers tokens on foreign chains that can be
|
||||
* burned + minted via the Circle Bridge.
|
||||
* @param encodedMessage Attested Wormhole governance message with the following
|
||||
* relevant fields:
|
||||
* - Field Bytes Type Index
|
||||
* - sourceToken 32 bytes32 35
|
||||
* - targetChainId 2 uint16 67
|
||||
* - targetToken 32 bytes32 69
|
||||
*/
|
||||
function registerTargetChainToken(bytes memory encodedMessage) public {
|
||||
bytes memory payload = verifyAndConsumeGovernanceMessage(encodedMessage, GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN);
|
||||
require(payload.length == GOVERNANCE_REGISTER_TARGET_CHAIN_TOKEN_LENGTH, "invalid governance payload length");
|
||||
|
||||
// registering target chain tokens should only be relevant for this contract's chain ID
|
||||
require(payload.toUint16(33) == chainId(), "invalid target chain");
|
||||
|
||||
// sourceToken at byte 35 (32 bytes, but last 20 is the address)
|
||||
address sourceToken = readAddressFromBytes32(payload, 35);
|
||||
require(isAcceptedToken(sourceToken), "source token not accepted");
|
||||
|
||||
// targetChain at byte 67
|
||||
uint16 targetChain = payload.toUint16(67);
|
||||
require(targetChain > 0 && targetChain != chainId(), "invalid target chain");
|
||||
|
||||
// targetToken at byte 69 (hehe)
|
||||
bytes32 targetToken = payload.toBytes32(69);
|
||||
require(targetToken != bytes32(0), "target token is zero address");
|
||||
|
||||
// update the targetAcceptedTokens mapping
|
||||
addTargetAcceptedToken(sourceToken, targetChain, targetToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice `upgradeContract` upgrades the implementation (logic) contract and
|
||||
* initializes the new implementation.
|
||||
|
|
|
@ -28,18 +28,14 @@ contract CircleIntegrationSetters is CircleIntegrationState {
|
|||
_state.circleTransmitterAddress = circleTransmitterAddress_;
|
||||
}
|
||||
|
||||
function setCircleTokenMinter(address circleTokenMinterAddress_) internal {
|
||||
_state.circleTokenMinterAddress = circleTokenMinterAddress_;
|
||||
}
|
||||
|
||||
function setEmitter(uint16 chainId_, bytes32 emitter) internal {
|
||||
_state.registeredEmitters[chainId_] = emitter;
|
||||
}
|
||||
|
||||
function addAcceptedToken(address token) internal {
|
||||
_state.acceptedTokens[token] = true;
|
||||
}
|
||||
|
||||
function addTargetAcceptedToken(address sourceToken, uint16 chainId, bytes32 targetToken) internal {
|
||||
_state.targetAcceptedTokens[sourceToken][chainId] = targetToken;
|
||||
}
|
||||
|
||||
function setChainIdToDomain(uint16 chainId_, uint32 domain) internal {
|
||||
_state.chainIdToDomain[chainId_] = domain;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {Context} from "@openzeppelin/contracts/utils/Context.sol";
|
|||
import {IWormhole} from "wormhole/interfaces/IWormhole.sol";
|
||||
import {ICircleBridge} from "../interfaces/circle/ICircleBridge.sol";
|
||||
import {IMessageTransmitter} from "../interfaces/circle/IMessageTransmitter.sol";
|
||||
import {ITokenMinter} from "../interfaces/circle/ITokenMinter.sol";
|
||||
|
||||
import {CircleIntegrationSetters} from "./CircleIntegrationSetters.sol";
|
||||
|
||||
|
@ -28,10 +29,18 @@ contract CircleIntegrationSetup is CircleIntegrationSetters, ERC1967Upgrade, Con
|
|||
setCircleBridge(circleBridgeAddress);
|
||||
setGovernance(governanceChainId, governanceContract);
|
||||
|
||||
IMessageTransmitter messageTransmitter = ICircleBridge(circleBridgeAddress).localMessageTransmitter();
|
||||
// Cache circle bridge
|
||||
ICircleBridge circleBridge = ICircleBridge(circleBridgeAddress);
|
||||
|
||||
// Circle message transmitter contract
|
||||
IMessageTransmitter messageTransmitter = circleBridge.localMessageTransmitter();
|
||||
setCircleTransmitter(address(messageTransmitter));
|
||||
setLocalDomain(messageTransmitter.localDomain());
|
||||
|
||||
// Circle token minter contract
|
||||
ITokenMinter tokenMinter = circleBridge.localMinter();
|
||||
setCircleTokenMinter(address(tokenMinter));
|
||||
|
||||
setEvmChain(block.chainid);
|
||||
|
||||
// set the implementation
|
||||
|
|
|
@ -30,21 +30,15 @@ contract CircleIntegrationStorage {
|
|||
/// @dev address of the Circle Message Transmitter on this chain
|
||||
address circleTransmitterAddress;
|
||||
|
||||
/// @dev address of the Circle Token Minter on this chain
|
||||
address circleTokenMinterAddress;
|
||||
|
||||
/// @dev mapping of initialized implementation (logic) contracts
|
||||
mapping(address => bool) initializedImplementations;
|
||||
|
||||
/// @dev Wormhole chain ID to known emitter address mapping
|
||||
mapping(uint16 => bytes32) registeredEmitters;
|
||||
|
||||
/// @dev Circle Bridge accepted token to boolean mapping
|
||||
mapping(address => bool) acceptedTokens;
|
||||
|
||||
/**
|
||||
* @dev Circle Bridge accepted token to target chain accepted token
|
||||
* (bytes32 zero-left-padded) mapping.
|
||||
*/
|
||||
mapping(address => mapping(uint16 => bytes32)) targetAcceptedTokens;
|
||||
|
||||
/// @dev Wormhole chain ID to Circle chain domain mapping
|
||||
mapping(uint16 => uint32) chainIdToDomain;
|
||||
|
||||
|
|
|
@ -40,6 +40,11 @@ interface ICircleIntegration {
|
|||
external
|
||||
returns (DepositWithPayload memory depositWithPayload);
|
||||
|
||||
function fetchLocalTokenAddress(uint32 sourceDomain, bytes32 sourceToken)
|
||||
external
|
||||
view
|
||||
returns (bytes32);
|
||||
|
||||
function encodeDepositWithPayload(DepositWithPayload memory message) external pure returns (bytes memory);
|
||||
|
||||
function decodeDepositWithPayload(bytes memory encoded) external pure returns (DepositWithPayload memory message);
|
||||
|
@ -68,8 +73,6 @@ interface ICircleIntegration {
|
|||
|
||||
function localDomain() external view returns (uint32);
|
||||
|
||||
function targetAcceptedToken(address sourceToken, uint16 chainId_) external view returns (bytes32);
|
||||
|
||||
function verifyGovernanceMessage(bytes memory encodedMessage, uint8 action)
|
||||
external
|
||||
view
|
||||
|
@ -82,9 +85,5 @@ interface ICircleIntegration {
|
|||
|
||||
function registerEmitterAndDomain(bytes memory encodedMessage) external;
|
||||
|
||||
function registerAcceptedToken(bytes memory encodedMessage) external;
|
||||
|
||||
function registerTargetChainToken(bytes memory encodedMessage) external;
|
||||
|
||||
function upgradeContract(bytes memory encodedMessage) external;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
// SPDX-License-Identifier: Apache 2
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {IMessageTransmitter} from "./IMessageTransmitter.sol";
|
||||
import {ITokenMinter} from "./ITokenMinter.sol";
|
||||
|
||||
interface ICircleBridge {
|
||||
/**
|
||||
|
@ -63,6 +64,8 @@ interface ICircleBridge {
|
|||
|
||||
function localMessageTransmitter() external view returns (IMessageTransmitter);
|
||||
|
||||
function localMinter() external view returns (ITokenMinter);
|
||||
|
||||
function remoteCircleBridges(uint32 domain) external view returns (bytes32);
|
||||
|
||||
// owner only methods
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
// SPDX-License-Identifier: Apache 2
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IMessageTransmitter {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: Apache 2
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title ITokenMinter
|
||||
* @notice interface for minter of tokens that are mintable, burnable, and interchangeable
|
||||
* across domains.
|
||||
*/
|
||||
interface ITokenMinter {
|
||||
function burnLimitsPerMessage(address token) external view returns (uint256);
|
||||
|
||||
function remoteTokensToLocalTokens(bytes32 sourceIdHash) external view returns (address);
|
||||
}
|
|
@ -1,11 +1,5 @@
|
|||
import {ethers} from "ethers";
|
||||
import {
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_AVAX,
|
||||
CHAIN_ID_ETH,
|
||||
tryNativeToUint8Array,
|
||||
ChainId,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import {tryNativeToUint8Array} from "@certusone/wormhole-sdk";
|
||||
import {MockGuardians} from "@certusone/wormhole-sdk/lib/cjs/mock";
|
||||
import {CircleGovernanceEmitter} from "../test/helpers/mock";
|
||||
import {abi as WORMHOLE_ABI} from "../../out/IWormhole.sol/IWormhole.json";
|
||||
|
@ -90,44 +84,6 @@ async function registerEmitterAndDomain() {
|
|||
expect(Buffer.compare(registeredEmitter, emitterAddress)).to.equal(0);
|
||||
}
|
||||
|
||||
async function registerAcceptedToken() {
|
||||
// MockGuardians and MockCircleAttester objects
|
||||
const guardians = new MockGuardians(
|
||||
await wormhole.getCurrentGuardianSetIndex(),
|
||||
[process.env.TESTNET_GUARDIAN_KEY!]
|
||||
);
|
||||
|
||||
const timestamp = getTimeNow();
|
||||
const chainId = Number(process.env.SOURCE_CHAIN_ID!);
|
||||
|
||||
// create unsigned registerAcceptedToken governance message
|
||||
const published = governance.publishCircleIntegrationRegisterAcceptedToken(
|
||||
timestamp,
|
||||
chainId,
|
||||
process.env.SOURCE_USDC_ADDRESS!
|
||||
);
|
||||
|
||||
// sign governance message with guardian key
|
||||
const signedMessage = guardians.addSignatures(published, [0]);
|
||||
|
||||
// register the token
|
||||
const receipt = await circleIntegration
|
||||
.registerAcceptedToken(signedMessage)
|
||||
.then((tx: ethers.ContractTransaction) => tx.wait())
|
||||
.catch((msg: string) => {
|
||||
// should not happen
|
||||
console.log(msg);
|
||||
return null;
|
||||
});
|
||||
expect(receipt).is.not.null;
|
||||
|
||||
// check contract state to verify the registration
|
||||
const accepted = await circleIntegration.isAcceptedToken(
|
||||
process.env.SOURCE_USDC_ADDRESS!
|
||||
);
|
||||
expect(accepted).is.true;
|
||||
}
|
||||
|
||||
async function updateFinality() {
|
||||
// MockGuardians and MockCircleAttester objects
|
||||
const guardians = new MockGuardians(
|
||||
|
|
|
@ -1,44 +1,20 @@
|
|||
import {expect} from "chai";
|
||||
import {ethers} from "ethers";
|
||||
import {tryNativeToUint8Array} from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_AVAX,
|
||||
CHAIN_ID_ETH,
|
||||
tryNativeToUint8Array,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
AVAX_USDC_TOKEN_ADDRESS,
|
||||
ETH_USDC_TOKEN_ADDRESS,
|
||||
GUARDIAN_PRIVATE_KEY,
|
||||
WORMHOLE_GUARDIAN_SET_INDEX,
|
||||
ETH_LOCALHOST,
|
||||
WALLET_PRIVATE_KEY,
|
||||
WALLET_PRIVATE_KEY_TWO,
|
||||
AVAX_LOCALHOST,
|
||||
ETH_FORK_CHAIN_ID,
|
||||
AVAX_FORK_CHAIN_ID,
|
||||
ETH_WORMHOLE_ADDRESS,
|
||||
AVAX_WORMHOLE_ADDRESS,
|
||||
} from "./helpers/consts";
|
||||
import {
|
||||
ICircleIntegration__factory,
|
||||
IUSDC__factory,
|
||||
IMockIntegration__factory,
|
||||
IWormhole__factory,
|
||||
} from "../src/ethers-contracts";
|
||||
import {ICircleIntegration__factory} from "../src/ethers-contracts";
|
||||
import {MockGuardians} from "@certusone/wormhole-sdk/lib/cjs/mock";
|
||||
import {RedeemParameters, TransferParameters} from "../src";
|
||||
import {findCircleMessageInLogs} from "../src/logs";
|
||||
|
||||
import {CircleGovernanceEmitter} from "./helpers/mock";
|
||||
import {
|
||||
getTimeNow,
|
||||
MockCircleAttester,
|
||||
readCircleIntegrationProxyAddress,
|
||||
readMockIntegrationAddress,
|
||||
findWormholeMessageInLogs,
|
||||
findRedeemEventInLogs,
|
||||
} from "./helpers/utils";
|
||||
import {getTimeNow, readCircleIntegrationProxyAddress} from "./helpers/utils";
|
||||
|
||||
describe("Circle Integration Registration", () => {
|
||||
// ethereum wallet, CircleIntegration contract and USDC contract
|
||||
|
@ -48,7 +24,6 @@ describe("Circle Integration Registration", () => {
|
|||
readCircleIntegrationProxyAddress(ETH_FORK_CHAIN_ID),
|
||||
ethWallet
|
||||
);
|
||||
const ethUsdc = IUSDC__factory.connect(ETH_USDC_TOKEN_ADDRESS, ethWallet);
|
||||
|
||||
// avalanche wallet, CircleIntegration contract and USDC contract
|
||||
const avaxProvider = new ethers.providers.StaticJsonRpcProvider(
|
||||
|
@ -59,29 +34,11 @@ describe("Circle Integration Registration", () => {
|
|||
readCircleIntegrationProxyAddress(AVAX_FORK_CHAIN_ID),
|
||||
avaxWallet
|
||||
);
|
||||
const avaxUsdc = IUSDC__factory.connect(AVAX_USDC_TOKEN_ADDRESS, avaxWallet);
|
||||
|
||||
// mock integration contract on avax
|
||||
const avaxMockIntegration = IMockIntegration__factory.connect(
|
||||
readMockIntegrationAddress(AVAX_FORK_CHAIN_ID),
|
||||
avaxWallet
|
||||
);
|
||||
|
||||
// MockGuardians and MockCircleAttester objects
|
||||
const guardians = new MockGuardians(WORMHOLE_GUARDIAN_SET_INDEX, [
|
||||
GUARDIAN_PRIVATE_KEY,
|
||||
]);
|
||||
const circleAttester = new MockCircleAttester(GUARDIAN_PRIVATE_KEY);
|
||||
|
||||
// Wormhole contracts
|
||||
const ethWormhole = IWormhole__factory.connect(
|
||||
ETH_WORMHOLE_ADDRESS,
|
||||
ethWallet
|
||||
);
|
||||
const avaxWormhole = IWormhole__factory.connect(
|
||||
AVAX_WORMHOLE_ADDRESS,
|
||||
avaxWallet
|
||||
);
|
||||
|
||||
describe("Registrations", () => {
|
||||
// produces governance VAAs for CircleAttestation contract
|
||||
|
@ -127,78 +84,6 @@ describe("Circle Integration Registration", () => {
|
|||
.then((bytes) => Buffer.from(ethers.utils.arrayify(bytes)));
|
||||
expect(Buffer.compare(registeredEmitter, emitterAddress)).to.equal(0);
|
||||
});
|
||||
|
||||
it("Should Register Accepted Token", async () => {
|
||||
const timestamp = getTimeNow();
|
||||
const chainId = await ethCircleIntegration.chainId();
|
||||
|
||||
// create unsigned registerAcceptedToken governance message
|
||||
const published =
|
||||
governance.publishCircleIntegrationRegisterAcceptedToken(
|
||||
timestamp,
|
||||
chainId,
|
||||
ETH_USDC_TOKEN_ADDRESS
|
||||
);
|
||||
|
||||
// sign governance message with guardian key
|
||||
const signedMessage = guardians.addSignatures(published, [0]);
|
||||
|
||||
// register the token
|
||||
const receipt = await ethCircleIntegration
|
||||
.registerAcceptedToken(signedMessage)
|
||||
.then((tx) => tx.wait())
|
||||
.catch((msg) => {
|
||||
// should not happen
|
||||
console.log(msg);
|
||||
return null;
|
||||
});
|
||||
expect(receipt).is.not.null;
|
||||
|
||||
// check contract state to verify the registration
|
||||
const accepted = await ethCircleIntegration.isAcceptedToken(
|
||||
ETH_USDC_TOKEN_ADDRESS
|
||||
);
|
||||
expect(accepted).is.true;
|
||||
});
|
||||
|
||||
it("Should Register Target Chain Token", async () => {
|
||||
const timestamp = getTimeNow();
|
||||
const chainId = await ethCircleIntegration.chainId();
|
||||
const targetChain = await avaxCircleIntegration.chainId();
|
||||
const targetToken = Buffer.from(
|
||||
tryNativeToUint8Array(AVAX_USDC_TOKEN_ADDRESS, "avalanche")
|
||||
);
|
||||
|
||||
// create unsigned registerTargetChainToken governance message
|
||||
const published =
|
||||
governance.publishCircleIntegrationRegisterTargetChainToken(
|
||||
timestamp,
|
||||
chainId,
|
||||
ETH_USDC_TOKEN_ADDRESS,
|
||||
targetChain,
|
||||
targetToken
|
||||
);
|
||||
|
||||
// sign governance message with guardian key
|
||||
const signedMessage = guardians.addSignatures(published, [0]);
|
||||
|
||||
// register the target token
|
||||
const receipt = await ethCircleIntegration
|
||||
.registerTargetChainToken(signedMessage)
|
||||
.then((tx) => tx.wait())
|
||||
.catch((msg) => {
|
||||
// should not happen
|
||||
console.log(msg);
|
||||
return null;
|
||||
});
|
||||
expect(receipt).is.not.null;
|
||||
|
||||
// check contract state to verify the registration
|
||||
const registeredTargetToken = await ethCircleIntegration
|
||||
.targetAcceptedToken(ETH_USDC_TOKEN_ADDRESS, targetChain)
|
||||
.then((bytes) => Buffer.from(ethers.utils.arrayify(bytes)));
|
||||
expect(Buffer.compare(registeredTargetToken, targetToken)).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Avalanche Fuji Testnet", () => {
|
||||
|
@ -239,78 +124,6 @@ describe("Circle Integration Registration", () => {
|
|||
.then((bytes) => Buffer.from(ethers.utils.arrayify(bytes)));
|
||||
expect(Buffer.compare(registeredEmitter, emitterAddress)).to.equal(0);
|
||||
});
|
||||
|
||||
it("Should Register Accepted Token", async () => {
|
||||
const timestamp = getTimeNow();
|
||||
const chainId = await avaxCircleIntegration.chainId();
|
||||
|
||||
// create unsigned registerAcceptedToken governance message
|
||||
const published =
|
||||
governance.publishCircleIntegrationRegisterAcceptedToken(
|
||||
timestamp,
|
||||
chainId,
|
||||
AVAX_USDC_TOKEN_ADDRESS
|
||||
);
|
||||
|
||||
// sign governance message with guardian key
|
||||
const signedMessage = guardians.addSignatures(published, [0]);
|
||||
|
||||
// register the token
|
||||
const receipt = await avaxCircleIntegration
|
||||
.registerAcceptedToken(signedMessage)
|
||||
.then((tx) => tx.wait())
|
||||
.catch((msg) => {
|
||||
// should not happen
|
||||
console.log(msg);
|
||||
return null;
|
||||
});
|
||||
expect(receipt).is.not.null;
|
||||
|
||||
// check contract state to verify the registration
|
||||
const accepted = await avaxCircleIntegration.isAcceptedToken(
|
||||
AVAX_USDC_TOKEN_ADDRESS
|
||||
);
|
||||
expect(accepted).is.true;
|
||||
});
|
||||
|
||||
it("Should Register Target Chain Token", async () => {
|
||||
const timestamp = getTimeNow();
|
||||
const chainId = await avaxCircleIntegration.chainId();
|
||||
const targetChain = await ethCircleIntegration.chainId();
|
||||
const targetToken = Buffer.from(
|
||||
tryNativeToUint8Array(ETH_USDC_TOKEN_ADDRESS, "avalanche")
|
||||
);
|
||||
|
||||
// create unsigned registerTargetChainToken governance message
|
||||
const published =
|
||||
governance.publishCircleIntegrationRegisterTargetChainToken(
|
||||
timestamp,
|
||||
chainId,
|
||||
AVAX_USDC_TOKEN_ADDRESS,
|
||||
targetChain,
|
||||
targetToken
|
||||
);
|
||||
|
||||
// sign governance message with guardian key
|
||||
const signedMessage = guardians.addSignatures(published, [0]);
|
||||
|
||||
// register the target token
|
||||
const receipt = await avaxCircleIntegration
|
||||
.registerTargetChainToken(signedMessage)
|
||||
.then((tx) => tx.wait())
|
||||
.catch((msg) => {
|
||||
// should not happen
|
||||
console.log(msg);
|
||||
return null;
|
||||
});
|
||||
expect(receipt).is.not.null;
|
||||
|
||||
/// check contract state to verify the registration
|
||||
const registeredTargetToken = await avaxCircleIntegration
|
||||
.targetAcceptedToken(AVAX_USDC_TOKEN_ADDRESS, targetChain)
|
||||
.then((bytes) => Buffer.from(ethers.utils.arrayify(bytes)));
|
||||
expect(Buffer.compare(registeredTargetToken, targetToken)).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,10 +29,7 @@ import {
|
|||
import {MockGuardians} from "@certusone/wormhole-sdk/lib/cjs/mock";
|
||||
import {RedeemParameters, TransferParameters} from "../src";
|
||||
import {findCircleMessageInLogs} from "../src/logs";
|
||||
|
||||
import {CircleGovernanceEmitter} from "./helpers/mock";
|
||||
import {
|
||||
getTimeNow,
|
||||
MockCircleAttester,
|
||||
readCircleIntegrationProxyAddress,
|
||||
readMockIntegrationAddress,
|
||||
|
@ -491,74 +488,6 @@ describe("Circle Integration Send and Receive", () => {
|
|||
expect(failed).is.true;
|
||||
});
|
||||
|
||||
it("Should Not Allow Transfers for Unregistered Target Tokens", async () => {
|
||||
// initialize governance module
|
||||
const governance = new CircleGovernanceEmitter();
|
||||
|
||||
// store EUROC address
|
||||
const eurocAddress = "0x53d80871b92dadeD34A4BdFA6838DdFC7f214240";
|
||||
const timestamp = getTimeNow();
|
||||
const chainId = await avaxCircleIntegration.chainId();
|
||||
|
||||
// publish an unsigned registerAcceptedToken governance message
|
||||
const published =
|
||||
governance.publishCircleIntegrationRegisterAcceptedToken(
|
||||
timestamp,
|
||||
chainId,
|
||||
eurocAddress
|
||||
);
|
||||
|
||||
// sign the governance message with the guardian key
|
||||
const signedMessage = guardians.addSignatures(published, [0]);
|
||||
|
||||
// register the token
|
||||
const receipt = await avaxCircleIntegration
|
||||
.registerAcceptedToken(signedMessage)
|
||||
.then((tx) => tx.wait())
|
||||
.catch((msg) => {
|
||||
// should not happen
|
||||
console.log(msg);
|
||||
return null;
|
||||
});
|
||||
expect(receipt).is.not.null;
|
||||
|
||||
// check state to confirm the token was registered
|
||||
const accepted = await avaxCircleIntegration.isAcceptedToken(
|
||||
eurocAddress
|
||||
);
|
||||
expect(accepted).is.true;
|
||||
|
||||
// define transferTokensWithPayload function arguments
|
||||
const params: TransferParameters = {
|
||||
token: eurocAddress,
|
||||
amount: amountFromAvax,
|
||||
targetChain: CHAIN_ID_ALGORAND as number, // unregistered chain
|
||||
mintRecipient: tryNativeToUint8Array(ethWallet.address, "ethereum"),
|
||||
};
|
||||
const batchId = 0; // opt out of batching
|
||||
const payload = Buffer.from("Sending an unregistered target token :)");
|
||||
|
||||
// try to transfer with an unregistered target token
|
||||
let failed: boolean = false;
|
||||
try {
|
||||
const receipt = await avaxCircleIntegration
|
||||
.transferTokensWithPayload(params, batchId, payload)
|
||||
.then(async (tx) => {
|
||||
const receipt = await tx.wait();
|
||||
return receipt;
|
||||
});
|
||||
} catch (e: any) {
|
||||
expect(
|
||||
e.error.reason,
|
||||
"execution reverted: target token not registered"
|
||||
).to.be.equal;
|
||||
failed = true;
|
||||
}
|
||||
|
||||
// confirm that the call failed
|
||||
expect(failed).is.true;
|
||||
});
|
||||
|
||||
it("Should Only Mint Tokens to the Mint Recipient", async () => {
|
||||
// define transferTokensWithPayload function arguments
|
||||
const params: TransferParameters = {
|
||||
|
|
|
@ -1,46 +1,22 @@
|
|||
import { expect } from "chai";
|
||||
import { ethers } from "ethers";
|
||||
import {expect} from "chai";
|
||||
import {ethers} from "ethers";
|
||||
import {tryNativeToUint8Array} from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
CHAIN_ID_ALGORAND,
|
||||
CHAIN_ID_AVAX,
|
||||
CHAIN_ID_ETH,
|
||||
tryNativeToUint8Array,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
AVAX_USDC_TOKEN_ADDRESS,
|
||||
ETH_USDC_TOKEN_ADDRESS,
|
||||
GUARDIAN_PRIVATE_KEY,
|
||||
WORMHOLE_GUARDIAN_SET_INDEX,
|
||||
ETH_LOCALHOST,
|
||||
WALLET_PRIVATE_KEY,
|
||||
WALLET_PRIVATE_KEY_TWO,
|
||||
AVAX_LOCALHOST,
|
||||
ETH_FORK_CHAIN_ID,
|
||||
AVAX_FORK_CHAIN_ID,
|
||||
ETH_WORMHOLE_ADDRESS,
|
||||
AVAX_WORMHOLE_ADDRESS,
|
||||
} from "./helpers/consts";
|
||||
import {
|
||||
ICircleIntegration__factory,
|
||||
IUSDC__factory,
|
||||
IMockIntegration__factory,
|
||||
IWormhole__factory,
|
||||
} from "../src/ethers-contracts";
|
||||
import { MockGuardians } from "@certusone/wormhole-sdk/lib/cjs/mock";
|
||||
import { RedeemParameters, TransferParameters } from "../src";
|
||||
import { findCircleMessageInLogs } from "../src/logs";
|
||||
import {ICircleIntegration__factory} from "../src/ethers-contracts";
|
||||
import {MockGuardians} from "@certusone/wormhole-sdk/lib/cjs/mock";
|
||||
|
||||
import { CircleGovernanceEmitter } from "./helpers/mock";
|
||||
import {
|
||||
getTimeNow,
|
||||
MockCircleAttester,
|
||||
readCircleIntegrationProxyAddress,
|
||||
readMockIntegrationAddress,
|
||||
findWormholeMessageInLogs,
|
||||
findRedeemEventInLogs,
|
||||
} from "./helpers/utils";
|
||||
import {CircleGovernanceEmitter} from "./helpers/mock";
|
||||
import {getTimeNow, readCircleIntegrationProxyAddress} from "./helpers/utils";
|
||||
|
||||
const { execSync } = require("child_process");
|
||||
const {execSync} = require("child_process");
|
||||
|
||||
describe("Circle Integration Implementation Upgrade", () => {
|
||||
// ethereum wallet, CircleIntegration contract and USDC contract
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import {ethers} from "ethers";
|
||||
|
||||
// ethereum goerli testnet fork
|
||||
export const ETH_LOCALHOST = "http://localhost:8545";
|
||||
export const ETH_LOCALHOST = "http://localhost:8546";
|
||||
export const ETH_FORK_CHAIN_ID = Number(process.env.ETH_FORK_CHAIN_ID!);
|
||||
export const ETH_WORMHOLE_ADDRESS = process.env.ETH_WORMHOLE_ADDRESS!;
|
||||
export const ETH_USDC_TOKEN_ADDRESS = process.env.ETH_USDC_TOKEN_ADDRESS!;
|
||||
export const ETH_CIRCLE_BRIDGE_ADDRESS = process.env.ETH_CIRCLE_BRIDGE_ADDRESS!;
|
||||
|
||||
// avalanche fuji testnet fork
|
||||
export const AVAX_LOCALHOST = "http://localhost:8546";
|
||||
export const AVAX_LOCALHOST = "http://localhost:8547";
|
||||
export const AVAX_FORK_CHAIN_ID = Number(process.env.AVAX_FORK_CHAIN_ID!);
|
||||
export const AVAX_WORMHOLE_ADDRESS = process.env.AVAX_WORMHOLE_ADDRESS!;
|
||||
export const AVAX_USDC_TOKEN_ADDRESS = process.env.AVAX_USDC_TOKEN_ADDRESS!;
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import { coalesceChainId, tryNativeToHexString } from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
GovernanceEmitter,
|
||||
MockEmitter,
|
||||
} from "@certusone/wormhole-sdk/lib/cjs/mock";
|
||||
import { ethers } from "ethers";
|
||||
import { DepositWithPayload, ICircleIntegration } from "../../src";
|
||||
import {GovernanceEmitter} from "@certusone/wormhole-sdk/lib/cjs/mock";
|
||||
import {ethers} from "ethers";
|
||||
|
||||
export interface Transfer {
|
||||
token: string;
|
||||
|
@ -18,34 +13,6 @@ export interface MockDepositWithPayload {
|
|||
fromAddress: Buffer;
|
||||
}
|
||||
|
||||
export class MockCircleIntegration extends MockEmitter {
|
||||
domain: number;
|
||||
foreignCircleIntegration: ICircleIntegration;
|
||||
|
||||
constructor(
|
||||
address: string,
|
||||
chain: number,
|
||||
domain: number,
|
||||
foreignCircleIntegration: ICircleIntegration
|
||||
) {
|
||||
super(tryNativeToHexString(address, "ethereum"), chain);
|
||||
this.domain = domain;
|
||||
this.foreignCircleIntegration = foreignCircleIntegration;
|
||||
}
|
||||
|
||||
async transferTokensWithPayload() {
|
||||
// mockParams: MockDepositWithPayload // payload: Buffer, // batchId: number, // transfer: Transfer,
|
||||
const foreign = this.foreignCircleIntegration;
|
||||
|
||||
const targetDomain = await foreign.localDomain();
|
||||
|
||||
// const depositWithPayload: DepositWithPayload = {
|
||||
// }
|
||||
// const encoded =
|
||||
// await this.interfaceContract.encodeDepositWithPayload({});
|
||||
}
|
||||
}
|
||||
|
||||
export class CircleGovernanceEmitter extends GovernanceEmitter {
|
||||
constructor(startSequence?: number) {
|
||||
super(
|
||||
|
@ -94,50 +61,6 @@ export class CircleGovernanceEmitter extends GovernanceEmitter {
|
|||
);
|
||||
}
|
||||
|
||||
publishCircleIntegrationRegisterAcceptedToken(
|
||||
timestamp: number,
|
||||
chain: number,
|
||||
tokenAddress: string,
|
||||
uptickSequence: boolean = true
|
||||
) {
|
||||
const payload = Buffer.alloc(32);
|
||||
payload.write(tryNativeToHexString(tokenAddress, "ethereum"), 0, "hex");
|
||||
return this.publishGovernanceMessage(
|
||||
timestamp,
|
||||
"CircleIntegration",
|
||||
payload,
|
||||
3,
|
||||
chain,
|
||||
uptickSequence
|
||||
);
|
||||
}
|
||||
|
||||
publishCircleIntegrationRegisterTargetChainToken(
|
||||
timestamp: number,
|
||||
chain: number,
|
||||
sourceTokenAddress: string,
|
||||
targetChain: number,
|
||||
targetTokenAddress: Buffer,
|
||||
uptickSequence: boolean = true
|
||||
) {
|
||||
const payload = Buffer.alloc(66);
|
||||
payload.write(
|
||||
tryNativeToHexString(sourceTokenAddress, "ethereum"),
|
||||
0,
|
||||
"hex"
|
||||
);
|
||||
payload.writeUInt16BE(targetChain, 32);
|
||||
payload.write(targetTokenAddress.toString("hex"), 34, "hex");
|
||||
return this.publishGovernanceMessage(
|
||||
timestamp,
|
||||
"CircleIntegration",
|
||||
payload,
|
||||
4,
|
||||
chain,
|
||||
uptickSequence
|
||||
);
|
||||
}
|
||||
|
||||
publishCircleIntegrationUpgradeContract(
|
||||
timestamp: number,
|
||||
chain: number,
|
||||
|
@ -149,7 +72,7 @@ export class CircleGovernanceEmitter extends GovernanceEmitter {
|
|||
timestamp,
|
||||
"CircleIntegration",
|
||||
payload,
|
||||
5,
|
||||
3,
|
||||
chain,
|
||||
uptickSequence
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue